These are my notes for the Creating Custom Sitefinity Modules webinar.
General Information
Intra-site Module versus Pluggable Modules
Both types are capable of achieving the same end functionality and the only difference is in the actual implementation.
Intra-Site modules - simpler/quicker to develop. Hard to transfer.
Pluggable modules - a bit more complex to implement. Easily transferred/installed.
Removing Existing Sitefinity Modules
To remove existing Sitefinity modules, remove the mapping from the <telerik><framework><modules> section in the ~/web.config file.
<telerik>
<framework>
<modules>
<add type="Telerik.Cms.Engine.GenericContentModule, Telerik.Cms.Engine"/>
<add type="Telerik.News.NewsModule, Telerik.News"/>
<add type="Telerik.Blogs.BlogsModule, Telerik.Blogs"/>
<add type="Telerik.Lists.ListModule, Telerik.Lists"/>
<add type="Telerik.Polls.PollModule, Telerik.Polls"/>
<add type="Telerik.Forums.ForumsModule, Telerik.Forums"/>
<add type="Telerik.Libraries.LibrariesModule, Telerik.Libraries"/>
<add type="Telerik.Events.EventsModule, Telerik.Events"/>
<add type="Telerik.Notifications.Newsletters.NewsletterModule, Telerik.Notifications"/>
</modules>
</framework>
</telerik>
Creating a new Module
All Sitefinity Modules must inherit from the Telerik.WebModule class. Classes that inherit from Telerik.WebModule are required to implement the following members:
- Name
- Title
- Description
- Controls
Below is the bare minimum amount of code needed to add a new module to Sitefinity.
Create a ~/App_Code/ContactsModule.cs containing the following code:
namespace Contacts
{
public class ContactsModule : Telerik.WebModule
{
public ContactsModule()
{
}
private System.Collections.Generic.IList<Telerik.Web.IToolboxItem> toolboxItems;
public override string Name
{
get { return "Contacts"; }
}
public override string Title
{
get { return "Contacts"; }
}
public override string Description
{
get { return "Module for managing web site contacts."; }
}
public override System.Collections.Generic.IList<Telerik.Web.IToolboxItem> Controls
{
get
{
return toolboxItems;
}
}
}
}
Adding a Control Panel & Command Panel
What is a ControlPanel and CommandPanel?
To add a Control Panel to the module, add the following code to ~/App_Code/ContactsModule.cs:
public override System.Web.UI.Control CreateControlPanel(System.Web.UI.TemplateControl parent)
{
return parent.LoadControl("~/Custom/Admin/Contacts/ControlPanel.ascx");
}
Create 2 new UserControls:
- ~/Custom/Admin/Contacts/ControlPanel.ascx
Implements the Telerik.Web.IControlPanel interface and references the CommandPanel.
- ~/Custom/Admin/Contacts/CommandPanel.ascx
Implements the Telerik.Web.ICommandPanel interface.
~/Custom/Admin/Contacts/ControlPanel.ascx:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="ControlPanel.ascx.cs" Inherits="Custom_Admin_Contacts_ControlPanel" %>
ControlPanel
~/Custom/Admin/Contacts/ControlPanel.ascx.cs:
using System;
using System.Web.UI;
using Telerik.Web;
public partial class Custom_Admin_Contacts_ControlPanel : UserControl, IControlPanel
{
protected void Page_Load(object sender, EventArgs e)
{
}
#region IControlPanel Members
public ICommandPanel[] CommandPanels
{
get { return new ICommandPanel[] {(ICommandPanel) Page.LoadControl("~/Custom/Admin/Contacts/CommandPanel.ascx")}; }
}
public void Refresh()
{
// Since it is a user control, causing a postback is enough to do a refresh
}
public string Status
{
get { return ""; }
}
public string Title
{
get { return "Contacts"; }
}
#endregion
}
~/Custom/Admin/Contacts/CommandPanel.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="CommandPanel.ascx.cs" Inherits="Custom_Admin_Contacts_CommandPanel" %>
CommandPanel
~/Custom/Admin/Contacts/CommandPanel.ascx.cs
using System;
using System.Web.UI;
using Telerik.Web;
public partial class Custom_Admin_Contacts_CommandPanel : UserControl, ICommandPanel
{
protected void Page_Load(object sender, EventArgs e)
{
}
public IControlPanel ControlPanel
{
get { return ctrlPnl; }
}
public string Name
{
get { return "CommandPanel Name"; }
}
public void Refresh()
{
// Since it is a user control, causing a postback is enough to do a refresh
}
public string Title
{
get { return "CommandPanel Title"; }
}
IControlPanel ctrlPnl;
}
Control Panel HTML Templates
The following HTML templates can be used to implement the look & feel used by other Sitefinity modules. These templates consist only of HTML. To make this code functional, replace the static HTML with server tags.
Blank - No items added.
<div class="ContorlPanelTitle">
<h1>
<a href="#">Contacts Module</a>
</h1>
</div>
<div class="ToolsAll">
<a href="#" class="CmsButLeft new"><strong class="CmsButRight light">Create an item</strong></a>
<div class="clear"></div>
</div>
<div class="workArea">
<h2 class="gridTitle">All Items</h2>
<div id="empty">
<h2 class="gridTitle">Nothing has been created yet.</h2>
<p><a class="mainLink" href="#"><strong>Create your first item</strong></a></p>
</div>
</div>
Add a New Item
<div class="ToolsAll">
<div class="backWrapp">
<a href="#" class="actions back">Cancel and go back</a>
</div>
</div>
<div class="workArea insert">
<div class="mainForm">
<p class="mand">* Mandatory fields</p>
<h3>Product Name</h3>
<fieldset class="set">
<div class="setIn title">
<input type="text" />
</div>
</fieldset>
<div class="bottom">
<div>
<!-- -->
</div>
</div>
<h3>Product Details</h3>
<fieldset class="set">
<div class="setIn">
<p class="example">Some additional instructions.</p>
<ol>
<li>
<label for="input2">Price</label>
<input id="input2" type="text" />
</li>
<li>
<label for="input3">Category</label>
<input id="input3" type="text" />
</li>
<li>
<label for="input4">Sale</label>
<input id="input4" type="text" />
</li>
</ol>
</div>
</fieldset>
<div class="bottom">
<div>
<!-- -->
</div>
</div>
<p class="button_area bot">
<a class="CmsButLeft okdark" href="#"><strong class="CmsButRight dark">Create this item</strong></a>
<span>or</span>
<a class="cmscclcmd" href="#">Cancel</a>
</p>
</div>
</div>
Command Panel
<h2>Example CommandPanel</h2>
<dl id="expMenu">
<dt id="all"><a class="sel" href="#">All forums</a></dt>
<dd>Here you can read, sort, edit and manage all forums.</dd>
<dt id="all"><a href="#">Categories</a></dt>
<dd>Here you can create and manage categories.</dd>
<dt id="globalPerm"><a href="#">Permissions</a></dt>
<dd>From here you can control who is allowed to read, edit or delete forums.</dd>
</dl>
More soon...
Final Result - Contacts Intra-site Module
~/App_Code/ContactsModule.cs
namespace Contacts
{
public class ContactsModule : Telerik.WebModule
{
public ContactsModule()
{
}
private System.Collections.Generic.IList<Telerik.Web.IToolboxItem> toolboxItems;
public override string Name
{
get { return "Contacts"; }
}
public override string Title
{
get { return "Contacts"; }
}
public override string Description
{
get { return "Module for managing web site contacts."; }
}
public override System.Collections.Generic.IList<Telerik.Web.IToolboxItem> Controls
{
get
{
return toolboxItems;
}
}
public override System.Web.UI.Control CreateControlPanel(System.Web.UI.TemplateControl parent)
{
return parent.LoadControl("~/Custom/Admin/Contacts/ControlPanel.ascx");
}
}
}
~/Custom/Admin/Contacts/ControlPanel.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="ControlPanel.ascx.cs" Inherits="Custom_Admin_Contacts_ControlPanel" %>
<div class="ContorlPanelTitle">
<h1><a href="#">Contacts Module</a></h1>
</div>
<div class="ToolsAll">
<asp:HyperLink ID="CreateNewButton1" class="CmsButLeft new" runat="server"><strong class="CmsButRight light">Create an item</strong></asp:HyperLink>
<div class="clear"></div>
</div>
<asp:LinqDataSource ID="LinqDataSource1" runat="server"
ContextTypeName="Contacts.Data.DatabaseDataContext" TableName="Contacts"
EnableDelete="True" EnableInsert="True" EnableUpdate="True">
</asp:LinqDataSource>
<asp:LinqDataSource ID="LinqDataSource2" runat="server"
ContextTypeName="Contacts.Data.DatabaseDataContext" EnableDelete="True"
EnableInsert="True" EnableUpdate="True" TableName="Contacts" Where="ID == @ID">
<WhereParameters>
<asp:ControlParameter ControlID="GridView1" DbType="Guid"
DefaultValue="SelectedValue" Name="ID" PropertyName="SelectedValue" />
</WhereParameters>
</asp:LinqDataSource>
<div class="workArea">
<asp:MultiView ID="MultiView1" runat="server">
<asp:View ID="EmptyView" runat="server">
<h2 class="gridTitle">All Items</h2>
<div id="empty">
<h2 class="gridTitle">Nothing has been created yet.</h2>
<p><asp:HyperLink ID="CreateNewButton2" class="mainLink" runat="server"><strong>Create your first item</strong></asp:HyperLink></p>
</div>
</asp:View>
<asp:View ID="ListView" runat="server">
<h2 class="gridTitle">All Contacts</h2>
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID" DataSourceID="LinqDataSource1" CssClass="listItems" GridLines="None" OnRowCommand="GridView1_RowCommand">
<Columns>
<asp:CommandField ShowSelectButton="true" ShowDeleteButton="true" />
<asp:BoundField DataField="FirstName" HeaderText="First Name"
SortExpression="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="Last Name"
SortExpression="LastName" />
<asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
<asp:BoundField DataField="EmailAddress" HeaderText="Email Address"
SortExpression="EmailAddress" />
<asp:BoundField DataField="Phone" HeaderText="Phone" SortExpression="Phone" />
</Columns>
</asp:GridView>
</asp:View>
<asp:View ID="EditContact" runat="server">
<div class="insert">
<div class="mainForm">
<h3>Contact Details</h3>
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ID" DataSourceID="LinqDataSource2" OnInit="FormView1_Init" OnDataBound="FormView1_DataBound" OnItemCommand="FormView1_ItemCommand">
<EditItemTemplate>
<fieldset class="set">
<div class="setIn">
<p class="example">Enter contact information in the form below:</p>
<ol>
<li>
<asp:Label AssociatedControlID="FirstNameTextBox" Text="First Name" runat="server" />
<asp:TextBox ID="FirstNameTextBox" runat="server" Text='<%# Bind("FirstName") %>' />
</li>
<li>
<asp:Label ID="Label1" AssociatedControlID="LastNameTextBox" Text="Last Name" runat="server" />
<asp:TextBox ID="LastNameTextBox" runat="server" Text='<%# Bind("LastName") %>' />
</li>
<li>
<asp:Label ID="Label2" AssociatedControlID="TitleTextBox" Text="Title" runat="server" />
<asp:TextBox ID="TitleTextBox" runat="server" Text='<%# Bind("Title") %>' />
</li>
<li>
<asp:Label ID="Label3" AssociatedControlID="EmailAddressTextBox" Text="Email Address" runat="server" />
<asp:TextBox ID="EmailAddressTextBox" runat="server" Text='<%# Bind("EmailAddress") %>' />
</li>
<li>
<asp:Label ID="Label4" AssociatedControlID="PhoneTextBox" Text="Phone" runat="server" />
<asp:TextBox ID="PhoneTextBox" runat="server" Text='<%# Bind("Phone") %>' />
</li>
</ol>
</div>
</fieldset>
<div class="bottom">
<div><!-- --></div>
</div>
<p class="button_area bot">
<asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert" CssClass="CmsButLeft okdark"><strong class="CmsButRight dark">Create this Contact</strong></asp:LinkButton>
<asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True" CommandName="Update" CssClass="CmsButLeft okdark"><strong class="CmsButRight dark">Update this Contact</strong></asp:LinkButton>
<span>or</span>
<asp:LinkButton ID="UpdateCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" CssClass="cmscclcmd" Text="Cancel"/>
</p>
</EditItemTemplate>
</asp:FormView>
</div>
</div>
</asp:View>
</asp:MultiView>
</div>
~/Custom/Admin/Contacts/ControlPanel.ascx.cs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using Telerik.Web;
public partial class Custom_Admin_Contacts_ControlPanel : UserControl, IControlPanel
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack == false)
{
// Default to List View
MultiView1.SetActiveView(ListView);
// The "action" parameter in our querystring allows us to toggle between modes.
// Kind of lame...but so was everything else.
SetViewToAction();
// Preset NavigateUrl of HyperLinks -- append "action" to current URL
SetNavigateUrls();
}
}
protected void Page_PreRender(object sender, EventArgs e)
{
// After updating data this flag tells us whether we need to Refresh our data.
// This refresh needs to take place here in Page_PreRender, which is why I'm
// using a temporary flag to make this happen.
if (RefreshData == true)
{
GridView1.DataBind();
}
// If our current view is ListView but we have no records, display the Empty view.
if (MultiView1.GetActiveView() == ListView && GridView1.Rows.Count == 0)
{
MultiView1.SetActiveView(EmptyView);
}
}
/// <summary>
/// I'm using this so I don't have to duplicate my entire EditItemTemplate in my FormView tag.
/// </summary>
protected void FormView1_Init(object sender, EventArgs e)
{
FormView1.InsertItemTemplate = FormView1.EditItemTemplate;
}
/// <summary>
/// I'm using this so I don't have to duplicate my entire EditItemTemplate in my FormView tag.
/// </summary>
protected void FormView1_DataBound(object sender, EventArgs e)
{
if (FormView1.CurrentMode == FormViewMode.Edit)
{
LinkButton InsertButton = FormView1.FindControl("InsertButton") as LinkButton;
LinkButton UpdateButton = FormView1.FindControl("UpdateButton") as LinkButton;
InsertButton.Visible = false;
UpdateButton.Visible = true;
}
else if (FormView1.CurrentMode == FormViewMode.Insert)
{
LinkButton InsertButton = FormView1.FindControl("InsertButton") as LinkButton;
LinkButton UpdateButton = FormView1.FindControl("UpdateButton") as LinkButton;
InsertButton.Visible = true;
UpdateButton.Visible = false;
}
}
/// <summary>
/// When an item is clicked (Select, Delete) in GridView, this is executed.
/// </summary>
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName.Equals("Select"))
{
// Switch to the FormView. We're now editing an item.
MultiView1.SetActiveView(EditContact);
FormView1.ChangeMode(FormViewMode.Edit);
}
else if (e.CommandName.Equals("Delete"))
{
RefreshData = true;
}
}
/// <summary>
/// Executed when a FormView command (Insert, Update, Cancel) is executed.
/// </summary>
protected void FormView1_ItemCommand(object sender, FormViewCommandEventArgs e)
{
MultiView1.SetActiveView(ListView);
RefreshData = true;
}
/// <summary>
/// Load the Command Panels associated with this Control Panel
/// </summary>
public ICommandPanel[] CommandPanels
{
get
{
return new ICommandPanel[] { (ICommandPanel)Page.LoadControl("~/Custom/Admin/Contacts/CommandPanel.ascx") };
}
}
public void Refresh()
{
// Since it is a user control, causing a postback is enough to do a refresh
}
public string Status
{
get { return ""; }
}
public string Title
{
get { return "Control Panel Status"; }
}
private void SetViewToAction()
{
string action = Request.QueryString["action"];
switch (action)
{
case "new":
MultiView1.SetActiveView(EditContact);
FormView1.ChangeMode(FormViewMode.Insert);
break;
default:
MultiView1.SetActiveView(ListView);
break;
}
}
private void SetNavigateUrls()
{
string Url = Request.ServerVariables["URL"] + "?module=" + Request.QueryString["module"];
CreateNewButton1.NavigateUrl = Url + "&action=new";
CreateNewButton2.NavigateUrl = Url + "&action=new";
}
private bool RefreshData = false;
}
~/Custom/Admin/Contacts/CommandPanel.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="CommandPanel.ascx.cs" Inherits="Custom_Admin_Contacts_CommandPanel" %>
<h2>CommandPanel</h2>
<dl id="expMenu">
<dt id="all"><asp:HyperLink ID="ViewAllButton" runat="server">View All Contacts</asp:HyperLink></dt>
<dd>Here you can read, sort, edit and manage all contacts.</dd>
<dt id="all"><asp:HyperLink ID="AddButton" runat="server">Add Contact</asp:HyperLink></dt>
<dd>Here you can create new contacts.</dd>
</dl>
~/Custom/Admin/Contacts/CommandPanel.ascx.cs
using System;
using System.Web.UI;
using Telerik.Web;
public partial class Custom_Admin_Contacts_CommandPanel : UserControl, ICommandPanel
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack == false)
{
// Preset NavigateUrl of HyperLinks -- append "action" to current URL
SetNavigateUrls();
}
}
public IControlPanel ControlPanel
{
get { return ctrlPnl; }
}
public string Name
{
get { return "CommandPanel Name"; }
}
public void Refresh()
{
// Since it is a user control, causing a postback is enough to do a refresh
}
public string Title
{
get { return "CommandPanel Title"; }
}
private void SetNavigateUrls()
{
string Url = Request.ServerVariables["URL"] + "?module=" + Request.QueryString["module"];
ViewAllButton.NavigateUrl = Url + "&action=list";
AddButton.NavigateUrl = Url + "&action=new";
}
IControlPanel ctrlPnl;
}