+1-888-365-2779
Try Now
More in this section
Categories
Bloggers
Blogs RSS feed

Working with ContentView designer (part 5): Implementing custom designer setting

by Ivan Osmak

In order to get this sample working, you will need to get the Sitefinity 3.5 Service Pack 1, so please go to your client.net account and grab your own version.

NOTE: You can scroll down to the end of post and grab the sample code with quick installation instructions

In this series I have already talked about TextSetting and ContentSetting controls, the two built-in settings that come with Sitefinity. In today’s post we are going to build a custom setting and plug it into the NewsView designer. The setting we are going to build will be called InlineCssSetting.

Yes, that is correct. The setting will allow end user to set the inline CSS for the target control directly from the browser; pretty cool, ehh? Take a look at the Figure 1 to see how the setting will look like and what do we expect it to do.


Figure 1: New InlineCssSetting is controlling the inline style of the publication date label

Implementing custom setting - high level overview

There are three classes that we will need to implement in order to get our custom setting working. While it sounds like a lot of work, you’ll see that it’s very simple and two out of these three classes are as simple as they can be. In order to understand fully why we need those three classes, let’s take a look at the Figure two which describes the custom setting workflow and then I’ll explain how it all fits together.

 
Figure 2: The basic workflow of the ContentViewDesigner setting control

If the diagram doesn’t look as simple as I hoped so, let me try to explain it in words.

ContentViewDesigner control renders the SettingControl. End user performs some actions on this control (e.g. enters the inline css), and setting control generates an instance of SettingValue control which carries the information user generated (e.g. “InlineCss=’color:red;font-weight:bold’”) and passes that SettingValue object to ContentView control to save it. When visitor of the website opens the page which has that ContentView control, ContentView control passes the SettingValue object to the SettingService along with the reference to the target control in order to apply the setting (e.g. set the style attribute with inline css on target control). After SettingService is done applying the setting, ContentView control renders the control with applied settings.

So, let’s define the purpose of each of the three classes:

  • SettingControl – defines the user interface of the setting control in the designer (e.g. label and textarea for entering the inline css)
  • SettingValue – object which carries the information on what settings user has made (e.g. style=”color:red;”)
  • SettingService – implements the logic of applying the setting on the target control. ContentView passes it the SettingValue object and the reference to the target control on which the setting should be applied

Detailed overview of the things we will need to perform in order to implement a custom setting

Following are steps that we will need to do to implement our custom setting

  • Implement SettingValue class
  • Create a template for the SettingControl
  • Implement SettingControl class
  • Implement SettingService class
  • Register SettingService class with Sitefinity
  • Add the setting to the designer template
  • Implement serializer for the settings at the application start

Admittedly, this process should and will be simplified. Due to some performance issues with XmlSerializer (which will be replaced by our custom serializer in the next versions) there are some steps that generally should not be performed. But bear with me for a while.

Implement SettingValue class

NOTE: Since all three classes that we are going to implement are tightly connected I generally put them in a single file. You can create a new file called InlineCssSetting.cs in the App_Code folder and create all three classes in that single file.

The SettingValue class represents a persistent object that you will need in order to apply settings. In our case the only property that we will need is a InlineCss string. So the implementation of this class will look like this:

        /// <summary>  
        /// Represents the value of the setting control. The purpose of this class is to transmit  
        /// the values you wish from the setting to the service and ApplySetting method  
        /// </summary>  
        public class InlineCssSettingValue : BaseSettingValue  
        {  
            /// <summary>  
            /// Gets or sets the format expression that should be used for formatting  
            /// </summary>  
            public string InlineCss  
            {  
                get 
                {  
                    return inlineCss;  
                }  
                set 
                {  
                    inlineCss = value;  
                }  
            }  
            string inlineCss;  
        }  
 

 

Note that this class needs to inherit from BaseSettingValue, so that ContentView can handle them as a collection of settings.  In case you need more properties, you can simply add them here (for example TextSettingValue object persists properties such as Visible, FormatType, Label, FormatString and so on).

Create a template for SettingControl

As you could see in the Figure 1, our SettingControl consists only of the label and textbox, so our template will be rather simple. While you can place your template anywhere, we generally place them all in following folder:

~/Sitefinity/Admin/ControlTemplates/Generic_Content/Design

Create a new user control in that folder and name it InlineCssSetting.ascx and paste following markup in it:

<li> 
    <asp:Label ID="settingTitle" runat="server"></asp:Label> 
    <br /> 
    <asp:TextBox ID="inlineCss" runat="server" TextMode="MultiLine">  
    </asp:TextBox> 
</li> 
 

Since all settings are displaying inside of a list, it makes sense to wrap the template inside of <li> tags.

Implement SettingControl class

This is the most complex class we need to implement and it is so because it is a control. Nothing new here, if you’ve ever created a composite control for Sitefinity everything we’ll be very clear. Open the InlineCssSetting.cs file we’ve created in the App_Code folder and paste the following code next to the SettingValue class we’ve already created:

    /// <summary>  
    /// Class representing inline css setting for the ContentView designer  
    /// </summary>  
    [ToolboxData("<{0}:InlineCssSetting runat=server></{0}:BasicSetting>")]  
    public class InlineCssSetting : BaseSetting  
    {
        #region Properties  
 
        /// <summary>  
        /// Gets or sets the value of this setting  
        /// </summary>  
        public override BaseSettingValue Value  
        {  
            get 
            {  
                EnsureChildControls();  
                if (value == null)  
                {  
                    value = new InlineCssSettingValue();  
                    value.SettingId = base.ID;  
                    value.TargetControlId = base.TargetID;  
                    value.SettingTypeName = typeof(InlineCssSettingValue).ToString();  
                }  
                return value;  
            }  
            set 
            {  
                this.value = value as InlineCssSettingValue;  
                LoadControlValue();  
            }  
        }  
 
        /// <summary>  
        /// Gets or sets the url of the InlineCssSetting control template path.  
        /// </summary>  
        public virtual string TemplatePath  
        {  
            get 
            {  
                object obj = ViewState["TemplatePath"];  
                if (obj != null)  
                    return (string)obj;  
                return "~/Sitefinity/Admin/ControlTemplates/Generic_Content/Design/InlineCssSetting.ascx";  
            }  
            set 
            {  
                ViewState["TemplatePath"] = value;  
            }  
        }  
 
        /// <summary>  
        /// Gets or sets the InlineCssSetting control template.  
        /// </summary>  
        [Browsable(false),  
        PersistenceMode(PersistenceMode.InnerProperty),  
        DefaultValue(typeof(ITemplate), ""),  
        Description("BaseSetting template"),  
        TemplateContainer(typeof(BaseSetting))  
        ]  
        public virtual ITemplate Template  
        {  
            get 
            {  
                if (template == null)  
                    template = ControlUtils.GetTemplate<DefaultTemplate>(TemplatePath);  
                return template;  
            }  
            set 
            {  
                template = value;  
            }  
        }
        #endregion  
 
        #region Base overrides  
 
        /// <summary>  
        /// Overriden. Called to populate the child control hierarchy. This is the main  
        /// method to render the control's markup, since it is a CompositeControl and contains  
        /// child controls.  
        /// </summary>  
        protected override void CreateChildControls()  
        {  
            // we call the base create child controls to fire the logic there,   
            base.CreateChildControls();  
 
            // we initialize this control  
            InitializeControl();  
 
            // set the setting title and hook up the text changed event handler  
            container.SettingTitle.Text = base.SettingTitle;  
            container.InlineCss.TextChanged += new System.EventHandler(InlineCss_TextChanged);  
              
            // finally add child controls to the hierarchy  
            Controls.Add(container);  
        }  
 
        ///<summary>  
        /// Gets the default value of the setting - the one when user has had no input yet  
        ///</summary>  
        ///<returns>BaseSettingValue with default properties</returns>  
        public override BaseSettingValue GetDefaultValue()  
        {  
            // use this method if you wish to set the default value of the setting.  
            // For example if you wish that all controls controlled by this setting  
            // are bold, you could set the InlineCss to "font-weight:bold;"  
            InlineCssSettingValue tValue = new InlineCssSettingValue();  
            tValue.SettingId = base.ID;  
            tValue.TargetControlId = base.TargetID;  
            tValue.SettingTypeName = typeof(InlineCssSettingValue).ToString();  
            tValue.InlineCss = "";  
            return tValue;  
        }
        #endregion  
 
        #region Private methods  
 
        /// <summary>  
        /// Creates a new instance of the container and instantiates the template  
        /// inside of it.  
        /// </summary>  
        private void InitializeControl()  
        {  
            container = new InlineCssSettingContainer(this);  
            Template.InstantiateIn(container);  
        }  
 
        /// <summary>  
        /// Loads the control values from the storage  
        /// </summary>  
        private void LoadControlValue()  
        {  
            EnsureChildControls();  
            InlineCssSettingValue val = Value as InlineCssSettingValue;  
            if (val != null)  
            {  
                TargetID = val.TargetControlId;  
                container.InlineCss.Text = val.InlineCss;  
            }  
        }  
 
        /// <summary>  
        /// Fires when user changes the inline css in the designer  
        /// </summary>  
        private void InlineCss_TextChanged(object sender, System.EventArgs e)  
        {  
            ((InlineCssSettingValue)(Value)).InlineCss = container.InlineCss.Text;  
            OnChanged(new BaseSettingEventArgs(Value));  
        }
        #endregion  
 
        #region Private fields  
 
        ITemplate template;  
        InlineCssSettingValue value;  
        InlineCssSettingContainer container;
        #endregion  
 
        #region Default templates  
 
        private class DefaultTemplate : ITemplate  
        {  
            public void InstantiateIn(Control container)  
            {  
                // todo implement class  
            }  
        }
        #endregion  
 
        #region Containers  
 
        /// <summary>  
        /// Container for the control which allows simply and strongly typed access to  
        /// child controls  
        /// </summary>  
        protected class InlineCssSettingContainer : GenericContainer<InlineCssSetting>  
        {  
            public InlineCssSettingContainer(InlineCssSetting owner)  
                : base(owner)  
            {  
 
            }  
 
            public ITextControl SettingTitle  
            {  
                get 
                {  
                    if (settingTitle == null)  
                        settingTitle = (ITextControl)base.FindRequiredControl<Control>("settingTitle");  
                    return settingTitle;  
                }  
            }  
 
            public IEditableTextControl InlineCss  
            {  
                get 
                {  
                    if (inlineCss == null)  
                        inlineCss = (IEditableTextControl)base.FindRequiredControl<Control>("inlineCss");  
                    return inlineCss;  
                }  
            }  
 
            ITextControl settingTitle;  
            IEditableTextControl inlineCss;  
        }
        #endregion  
    }  
 

Though I have commented most of the code, let me walk you through some of the more important parts.

This class needs to inherit from the BaseSetting class. The Value property is the one which will be called by designer to receive the value of the setting when user changes it, and the one that designer will set so that the user interface can be set (e.g. to populate the textarea with the inline css that user saved previous time).

GetDefaultValue override provides you ability to set the default value of the setting which will be persisted even if user doesn’t change anything. For example in TextSetting we may decide that we want some element to be invisible by default, so this is where would do that.
LoadControlValue is very similar to DataBind method. Basically you receive some data and you display it in the user interface.

Finally we have created an event handler for the text changed of the InlineCss control, so that we can notify designer that user has changed something in the setting and that the new value should be saved.

Implement SettingService class

The final piece of our classes puzzle. Let me first show you the code, because it everything will make much more sense once you see the code. Open the InlineCssSetting.cs file we’ve created in the App_Code folder and add this class to it:

    /// <summary>  
    /// Represent a class that handles applying PresentationSetting of the ContentView control   
    /// </summary>  
    public class InlineCssSettingService : IContentViewSettingService  
    {  
        /// <summary>  
        /// Applies the settings to the child control inside of a ContentView control.  
        /// </summary>  
        /// <param name="sender">ContentView control which sent the request for applying settings</param>  
        /// <param name="settingValue">Value of the setting</param>  
        /// <param name="container">Container of the control to which settings will be applied</param>  
        /// <returns>True if service has applied settings, otherwise false</returns>  
        public virtual bool ApplySettings(ContentView sender, BaseSettingValue settingValue, Control container)  
        {  
            // we check if the passed settingValue is of type InlineCssSettingValue and if  
            // so do the magic  
            InlineCssSetting.InlineCssSettingValue val = settingValue as InlineCssSetting.InlineCssSettingValue;  
            if (val != null)  
            {  
                // apply inline css, possible only for WebControls - make sure you don't  
                // try to apply this setting to Literal, because it is not a WebControl  
                WebControl ctrl = container.FindControl(val.TargetControlId) as WebControl;  
                if (ctrl != null)  
                {  
                    ctrl.Attributes["style"] = val.InlineCss;  
                }  
                return true;  
            }  
            return false;  
        }  
    }  
 

This class needs to implement IContentViewSettingService interface, so that ContentView can call it by taking advantage of polymorphism. The class implements only one method, namely ApplySettings. ContentView will pass the reference to itself, the value object and container to this class. Then in this method, we try to case the settingValue to the InlineCssSettingValue and basically check that this service knows how to apply the passed setting value. If we succeed, we look for the target control and add it style attribute which is set to the inline css. If setting was successfully applied we return true, otherwise false.

Register SettingService class with Sitefinity

In order for ContentView control to know what setting services are available, we have to to register our new InlineCssSettingService. This we can done simply by pasting following line in the web.config as a child of telerik/cmsEngine/contentViewSettings:

<add type="Samples.ContentViewDesignerSettings.InlineCssSettingService, App_Code" /> 

 

Add the setting to the designer template

Now, that we are almost done, we are going to use our new setting in one of the designers. We’ll do it for the NewsView designer. Open following file:

~/Sitefinity/Admin/ControlTemplates/News/Design/NewsViewControlDesigner.ascx

Start by registering the setting control at the top of the file, so that you paste this line:

<%@ Register Namespace="Samples.ContentViewDesignerSettings" Assembly="App_Code" TagPrefix="customDesignSettings" %> 

Now, add the setting somewhere in the master settings collection. Paste the following line:

<customDesignSettings:InlineCssSetting   
                                              ID="InlineCssSetting1" 
                                              SettingTitle="Date CSS" 
                                              TargetID="Publication_Date" 
                                              runat="server">  
                                              </customDesignSettings:InlineCssSetting> 
 

NOTE: The step I am about to describe is not generally required, but in case with our inline css setting it is. Namely, Target control Publication_Date is by default Literal (which cannot have style attribute), so we’ll have to open the NewsView template and change the literal to label in order for our setting to work. Open following file:

~/Sitefinity/ControlTemplates/News/Modes/ListPageMaster.ascx

And replace this line:
<asp:Literal ID="Publication_Date" runat="server" Text="{0}" /> 
With this line:
<asp:Label ID="Publication_Date" runat="server" /> 

Almost there… one more thing to do.

Implement serializer for the settings at the application start

This step will not be necessary in next versions, but for now we have to do it. Namely, we have to cache the xml serializer with the new setting at the application start. Due to some specifics of XmlSerializer failing to do so would result in quite severe performance problems. So, open your Global.asax page and paste following code in the Application_Start handler:

Type[] settingValueTypes = new Type[] {  
                                                                       typeof(Telerik.Cms.Engine.WebControls.Design.Settings.  
                                                                           TextSettingValue),  
                                                                       typeof(Telerik.Cms.Engine.WebControls.Design.Settings.  
                                                                           ContentSettingValue),  
                                                                       typeof(Samples.ContentViewDesignerSettings.  
                                                                           InlineCssSetting.InlineCssSettingValue)  
                                              };  
        XmlSerializer serializer = new XmlSerializer(typeof(List<BaseSettingValue>), settingValueTypes);  
        HttpContext.Current.Cache["SettingsSerializedTypes"] = settingValueTypes;  
        HttpContext.Current.Cache["SettingsSerializer"] = serializer;  
 

So, what are we doing here? We are caching all value types our designers will use together with the serializer itself. Notice that you need to add the built-in value types (TextSettingValue and ContentSettingValue) along with the custom types you add.

Save everything and you are done. Give your new setting a try by editing NewsView control.

Sample code and quick installation guide

In case you are in a hurry or simply not interested in all details, here is the sample code. To get it working please do following:

1. Download the project and uncompress it.
2. Paste following line in the web.config of your application as a child of telerik/cmsEngine/contentViewSettings:

<add type="Samples.ContentViewDesignerSettings.InlineCssSettingService, App_Code" /> 

3. Paste the following lines in the Global.asax file of your application inside of the Application_Start handler:

Type[] settingValueTypes = new Type[] {  
                                                                       typeof(Telerik.Cms.Engine.WebControls.Design.Settings.  
                                                                           TextSettingValue),  
                                                                       typeof(Telerik.Cms.Engine.WebControls.Design.Settings.  
                                                                           ContentSettingValue),  
                                                                       typeof(Samples.ContentViewDesignerSettings.  
                                                                           InlineCssSetting.InlineCssSettingValue)  
                                              };  
        XmlSerializer serializer = new XmlSerializer(typeof(List<BaseSettingValue>), settingValueTypes);  
        HttpContext.Current.Cache["SettingsSerializedTypes"] = settingValueTypes;  
        HttpContext.Current.Cache["SettingsSerializer"] = serializer;  
 

4. Copy App_Code and Sitefinity folders from the project you have downloaded to your project. If prompted to overwrite click yes (do not test this on an actual project obviously – use empty project)
5. Go to edit NewsView control and try out your new InlineCssSetting

Let me know if you run into problems or have a suggestion by leaving a comment. Thanks.

Leave a comment