How to effectively customize Kentico CMS UI
UI customization is an often topic in our discussions with clients. I put together a few examples and best practices of how you can do things so they are upgrade procedure proof and done as easy as you can.
Hi there,
I am sure some of you have experiences with customizing of the UI, and then having trouble with upgrade procedure, where you need to take care about anything you have changed, do you? Some of the others probably didn't even know that it is possible to customize the UI and the rest just didn't need it or was afraid of it. So this post should be helpful.
Connection points
There is a group of connection points specifically placed for the UI customization reasons. You can find it in you project in
~/App_Code/Global/CMS folder. As you can see it is in the recommended list of folders to use:
http://devnet.kentico.com/docs/devguide/folder_structure_and_importexport.htm
This way you can get the upgrade prodedure proof and import/export proof customization.
The available events are following:
-
CMSApplication.cs - List of the events within application life cycle where you can initialize your modules or do other global things.
-
CMSCustom.cs - Where you can define your own custom macro handler, see http://devnet.kentico.com/docs/devguide/appendix_a___macro_expressions.htm for details.
-
CMSPageEvents.cs - Page life cycle events, where you can customize your pages. Any page that inherits CMSPage (any UI page) calls these events. This does not apply to the live site (content) pages since they are custom and are using custom web part by their nature.
-
CMSRequest.cs - Request life cycle events, where you can customize the request processing.
-
CMSSession.cs - Session events, where you can do additional tasks if session changes.
-
CMSUserControlEvents.cs - Where you can customize most of the the UI user controls of Kentico CMS (I say most, because there can be few which do not fire these events, in general anything that inherits CMSUserControl does)
So this is the list of events, let's go to the customization itself. Just before you do that, I recommend you to run your project in the debug mode to see how and when the events are called. For particular customization, you just need to choose the right event for the action so you may need to check if the data you are about to change are already present.
Customization of existing page
I will show you a simple example (actually two) how to add the tab to the existing page. First, we add the Google tab to the CMSDesk header. It is a page (/CMSDesk/Header.aspx) and it is loading the tabs in the load event. So we use the
CMSPageEvents and
BeforePreRender (next event called) to modify the tabs:
using CMS.UIControls;
...
/// <summary>
/// Fires before page PreRender
/// </summary>
/// <returns>Returns true if default action should be performed</returns>
public static bool BeforePagePreRender(object sender, EventArgs e)
{
// Add your custom actions
CMSPage page = (CMSPage)sender;
switch (page.RelativePath.ToLower())
{
case "/cmsdesk/header.aspx":
BasicTabControl tabControl = (BasicTabControl)page["TabControl"];
// Add tabs
string[,] tabs = BasicTabControl.GetTabsArray(1);
tabs[0, 0] = "Google";
tabs[0, 2] = "http://www.google.com";
tabControl.AddTabs(tabs);
break;
}
return true;
}
Sometimes (well, mostly :-) ) the page uses master page so you need to reference the tabs from the master page like this:
// Add your custom actions
CMSPage page = (CMSPage)sender;
switch (page.RelativePath.ToLower())
{
case "/cmsmodules/bizforms/tools/bizform_edit_header.aspx":
BasicTabControl tabControl = ((CMSMasterPage)page.Master).Tabs;
// Add tabs
string[,] tabs = BasicTabControl.GetTabsArray(1);
tabs[0, 0] = "Google";
tabs[0, 2] = "http://www.google.com";
tabControl.AddTabs(tabs);
break;
}
return true;
This second one is in the properties of the BizForm. Try this code and see for yourselves. What we do is to use the
CMSPage class (the base class) and use some information from it. The property
TabControl in the first case (you may also find the control by searching the controls collection) and the master page property
Tabs in second case.
You can also add specific controls to the page like this:
/// <summary>
/// Fires after page Load
/// </summary>
public static void AfterPageLoad(object sender, EventArgs e)
{
// Add your custom actions
CMSPage page = (CMSPage)sender;
switch (page.RelativePath.ToLower())
{
case "/cmsdesk/header.aspx":
page.Controls[0].Controls.AddAt(0, new LiteralControl("<span style=\"position: absolute; z-index: 1000;\">My control</span>"));
break;
}
}
Easy, isn't it? I think it is, so let's go further ...
Replacing the page with your customized page
If you customize the page directly, you have to take care that the upgrade procedure doesn't overwrite it. So it would always be better to have a copy of the page and customize the copy ... But how do you replace the original page without modifying another page? Luckily, it is not that easy with page events. We just transfer the page to the different one (we could do the similar in the request events in beginrequest or so):
/// <summary>
/// Fires before page PreInit
/// </summary>
/// <returns>Returns true if default action should be performed</returns>
public static bool BeforePagePreInit(object sender, EventArgs e)
{
// Check the path of current file
string path = HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.ToLower();
switch (path)
{
// Custom user details page
case "~/cmssitemanager/administration/users/user_edit_general.aspx":
// Just change the path to the file you need
HttpContext.Current.Server.Transfer("~/cmsglobalfiles/my_user_edit_general.aspx");
break;
}
return true;
}
I am catching the PreInit event and transfer the execution to my own file. The file is in the
~/CMSGlobalFiles folder to be included into the export package. It is just the copy of the original page, with changed class name and modified with some additional code and controls. Notice, that I am not using the RelativePath property, because it would cause a loop (it is constant during the request processing). I might as well do the
Response.Redirect to use completely different application in that part of the user interface.
Customizing the UI user controls
You can do similar things to the UI controls, just not the redirection (correct me if I am wrong) so in case you need to replace the entire control with your own customized version, I would recommend to customize the page that contains it. What you can do is modify the controls collection of the user control like in the page like this (modify the file
CMSUserControlEvents.cs):
/// <summary>
/// Fires after UserControl Load
/// </summary>
public static void AfterUserControlLoad(object sender, EventArgs e)
{
// Add your custom actions
if (sender is CMSUserControl)
{
CMSUserControl userControl = (CMSUserControl)sender;
switch (userControl.RelativePath.ToLower())
{
case "/cmsadmincontrols/ui/pagetitle.ascx":
// Modify the control
userControl.Controls.AddAt(0, new LiteralControl("<a href=\"http://www.kentico.com\">Visit Kentico.com</a>"));
break;
}
}
}
This will add link to Kentico.com to all of your page titles. Not too useful modification, but you get the concept now (hopefully :-) ) ... Of course you can work with the control in many different ways, that is entirely up to you.
Customizing forms (form controls)
Customizing the pages and controls can be too complicated in some cases, mostly when you need to customize the forms based on the document type or other definitions. One of the oldest concept for this (much older than those events above) are the
Form controls. If you look to your
Site manager -> Development -> Form controls, you can see the list of the controls and that you can configure particular ones for specific dialogs. You can also register one and that's the place for our customization. The default form controls are in the folder
~/CMSFormControls or under
~/CMSModules/... but you can place them anywhere. They will be always part of your export package if you export the object (they contain the path to the file).
So create a new folder called
~/MyFormControls and create a new control
myformcontrol.ascx in it, with the ASCX code:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="myformcontrol.ascx.cs" Inherits="MyFormControls_myformcontrol" %>
<asp:TextBox runat="server" ID="txtValue" />
<asp:RequiredFieldValidator ID="valValue" runat="server" ErrorMessage="Client side validation says that the value cannot be empty!" ControlToValidate="txtValue" />
And the code behind:
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using CMS.FormControls;
public partial class MyFormControls_myformcontrol : FormEngineUserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
/// <summary>
/// Gets / sets the value
/// </summary>
public override object Value
{
get
{
return this.txtValue.Text;
}
set
{
this.txtValue.Text = (string)value;
}
}
/// <summary>
/// Returns true if the value is valid
/// </summary>
public override bool IsValid()
{
if (this.txtValue.Text.Length == 5)
{
return true;
}
else
{
this.ValidationError = "Server side validation says that the value length must be 5 characters.";
return false;
}
}
}
You can see you can do anything like with standard user control, you just inherit from
FormEngineUserControl. We use both client side and server side validation in this case (just to show you how that works). Now just register the control in the Site manager, with the path
~/MyFormControls/myformcontrol.ascx, for the
Text fields and for instance in the
Document types.
Now when you edit the text field in the document type, you have one more control to select. If you select it and edit the document of that type, you can see how it works (it is not that nice, but it works :-) )
Customize the live site
For customizing the live site, you can just type the ASPX code in the tranformation, page layout or create your custom web parts, or develop inline controls, you have direct ways how to do that our of the box, see these topics for that:
http://devnet.kentico.com/docs/devguide/developing_web_parts.htm
http://devnet.kentico.com/docs/devguide/how_to_develop_inline_controls.htm
http://devnet.kentico.com/docs/devguide/adding_custom_functions_to_transformations.htm
http://devnet.kentico.com/docs/devguide/page_layouts.htm
http://devnet.kentico.com/docs/devguide/creating_a_new_aspx_page_template.htm
and many others ...
What else do you need?
I suppose you can customize virtually anything (from the UI point of view) with these few concepts. Let me know in the comments if you encountered anything that cannot be handled by one of these, I will point you to the right direction or think about how we could improve the current experience if something is too complicated.