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

Creating Advanced Sitefinity 4 Widget Control Designers

by Josh Morales

In the article Anatomy of a Sitefinity 4 Widget we looked at the basic building blocks for building a Sitefinity Widget and its associated Control Designer.

Today we’ll take a look at a new example, making use of different HTML elements such as checkboxes, drop-down menus, and radio buttons. Also, because the Telerik AJAX RadControls have a robust client-side API, we’ll see how easy it is to incorporate those controls into a widget control designer as well.

Note: In today’s example, we’ll be building a Simple Widget, that is, one that is added directly to the Sitefinity project itself. We’ll convert this to a Compiled Widget in a future post. For more on the differences between the two, see the previous article: Anatomy of a Sitefinity 4 Widget.

Sample Widget

For this example we’ll be using a simple widget that displays some basic information. It doesn’t have much practical use, but it does demonstrate how you can use these elements in real-world scenarios.

First let’s look at the Widget class itself:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
using Telerik.Web.UI;
using Telerik.Sitefinity.Web.UI.ControlDesign;
using Telerik.Sitefinity.Modules.Pages.Web.UI;

namespace SitefinityWebApp.Widgets.SampleWidget
{
    [RequireScriptManager(true)]
    [ControlDesigner(typeof(SampleWidgetDesigner))]
    public partial class SampleWidget : System.Web.UI.UserControl
    {
        public enum Gender { Male, Female }


        // Defaults
        private string name = "John Doe";
        private string state = "Texas";
        private bool single = true;
        private Gender myGender = Gender.Male;
        private decimal rating = 3.5M;
        private DateTime birthday = DateTime.Now;
        private Color myColor = Color.Red;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        public string State
        {
            get { return state; }
            set { state = value; }
        }
        public bool Single
        {
            get { return single; }
            set { single = value; }
        }
        public Gender MyGender
        {
            get { return myGender; }
            set { myGender = value; }
        }
        public decimal Rating
        {
            get { return rating; }
            set { rating = value; }
        }
        public DateTime Birthday
        {
            get { return birthday; }
            set { birthday = value; }
        }
        public Color MyColor
        {
            get { return myColor; }
            set { myColor = value; }
        }
        public string HexColor
        {
            get { return "#" + MyColor.R.ToString("X2") + MyColor.G.ToString("X2") + MyColor.B.ToString("X2"); }
            set
            {
                var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(Color));
                MyColor = (Color)converter.ConvertFromString(value);
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            NameLabel.Text = Name;
            StateLabel.Text = State;
            SingleLabel.Visible = !Single;
            GenderLabel.Text = MyGender.ToString();
            StarRating.Value = Rating;
            BirthdayLabel.Text = Birthday.ToLongDateString();
            ColorLabel.Text = MyColor.Name;
            ColorLabel.ForeColor = MyColor;
        }
    }
}

Here we just defined some properties of different types, including string, bool, and enumeration, as well as properties that will go with our RadControls such as DateTime and Color.

Also take note of the added HexColor property, which wraps the MyColor field. This is done because the RadColorPicker requires Hex values, but our MyColor field uses the System.Drawing.Color type. Using this field allows us to convert the values between the two types.

We set a few defaults, and finally in the Load method, pass them to the template to be rendered. Here is the template for the widget front-end:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SampleWidget.ascx.cs" Inherits="SitefinityWebApp.Widgets.SampleWidget.SampleWidget" %>

<p>Hello, my name is <asp:Literal ID="NameLabel" runat="server" /> and I live in <asp:Literal ID="StateLabel" runat="server" />.</p>
<p>I am <asp:Literal ID="GenderLabel" runat="server" /> and am currently <asp:Literal ID="SingleLabel" runat="server" Text="not" /> single.</p>
<p>My birthday is on <asp:Literal ID="BirthdayLabel" runat="server" /> and my favorite color is <asp:Label ID="ColorLabel" runat="server" />.</p>
<p>My rating: <telerik:RadRating ID="StarRating" runat="server" Precision="Half" /></p>

Now we can add our Control Designer class. Remember that this simply wires up the Widget to its template and associated script file, as well as telling the Designer to render in Simple mode.

using System.Collections.Generic;
using Telerik.Sitefinity.Web.UI.ControlDesign;
using System.Web.UI;

namespace SitefinityWebApp.Widgets.SampleWidget
{
    public class SampleWidgetDesigner : ControlDesignerBase
    {
        protected override void InitializeControls(Telerik.Sitefinity.Web.UI.GenericContainer container)
        {
            base.DesignerMode = ControlDesignerModes.Simple;
        }

        public override string LayoutTemplatePath
        {
            get { return _layoutTemplatePath; }
            set { _layoutTemplatePath = value; }
        }

        protected override string LayoutTemplateName
        {
            get { return string.Empty; }
        }

        public override IEnumerable<ScriptReference> GetScriptReferences()
        {
            // get script collection
            var scripts = base.GetScriptReferences() as List<ScriptReference>;
            if (scripts == null) return base.GetScriptReferences();

            scripts.Add(new ScriptReference(_scriptReference));

            return scripts.ToArray();
        }

        private string _layoutTemplatePath = "~/Widgets/SampleWidget/SampleWidgetDesigner.ascx";
        private string _scriptReference = "~/Widgets/SampleWidget/SampleWidgetDesigner.js";
    }
}

Control Designer Templates and Code Behind

In most cases, the control designer template is a simple ascx file, used only to layout the HTML elements used by the designer.

In this case, however I wanted to demonstrate that you can indeed use a full ASP.NET User Control, complete with code-behind to setup the control designer template. In this example, we’ll use it to populate the States drop-down menu from a custom xml file (also available in the downloadable project below).

So we need to add a new User Control to our project and include the HTML, ASP.NET and RadControl elements that will drive our designer. Here is the complete front-end of the control.

Note: to simplify interaction with jQuery, I’ve taken advantage of the ClientIDMode to have the client-side names match the server side. We’ll see how this makes things easier when we look at the JavaScript file for our designer.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SampleWidgetDesigner.ascx.cs" Inherits="SitefinityWebApp.Widgets.SampleWidget.SampleWidgetDesigner1" %> <%@ Register Assembly="Telerik.Sitefinity" Namespace="Telerik.Sitefinity.Web.UI.Fields" TagPrefix="sf" %>

 

<div class="sfContentViews"> <div> <div> <div id="groupSettingPageSelect"> <ul class="sfTargetList"> <li> <label for="Name" class="sfTxtLbl">Name</label> <input type="text" id="Name" class="sfTxt" /> </li> <li> <label for="State" class="sfTxtLbl">State</label> <asp:DropDownList ID="State" runat="server" ClientIDMode="Static" /> </li> <li> <label for="Single" class="sfTxtLbl">Single?</label> <input type="checkbox" id="Single" /> </li> <li> <label class="sfTxtLbl">Gender</label> <label for="Male">Male</label> <input type="radio" name="gender" id="Male" value="Male" /> <label for="Female">Female</label> <input type="radio" name="gender" id="Female" value="Female" /> </li> <li> <label for="Birthday" class="sfTxtLbl">Birthday</label> <telerik:RadDatePicker ID="Birthday" runat="server" ClientIDMode="Static" /> </li> <li> <label>Rating</label> <telerik:RadRating ID="Rating" runat="server" Precision="Half" ClientIDMode="Static" /> </li> <li> <label for="HexColor" class="sfTxtLbl">Favorite Color</label> <telerik:RadColorPicker ID="HexColor" runat="server" ClientIDMode="Static" /> </li> </ul> </div> </div> </div> </div>

Please note that Microsoft recommends using ClientIDMode=Static only for static control. RadControls on the other hand are controls with complex hierarchies of child controls and templates so setting their ClientID mode to static sometimes might break their functionality. This behavior could be observed if you are using more then one RadDatePicked. You could set the ClientIDMode for the RadDatePickers to "Auto" so you could prevent breaking of your RadControls.

Notice how I’ve made use of some Telerik controls, as well as the ASP.NET DropDownList control. To populate the list, we simply use the control’s codebehind:

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Xml; using System.Data; namespace SitefinityWebApp.Widgets.SampleWidget { public partial class SampleWidgetDesigner1 : System.Web.UI.UserControl { protected void Page_Load(object sender, EventArgs e) { if (IsPostBack) return; var ds = new DataSet();

// read states data file and bind to drop-down menu ds.ReadXml(MapPath("~/Widgets/SampleWidget/States.xml")); State.DataTextField = State.DataValueField = "name"; State.DataSource = ds; State.DataBind(); } } }

This technique could be used to perform any kind of server-side processing, such as binding to a database table, retrieving content from web services, or anything else that your control might need that would be difficult to retrieve on the client-side.

Binding With JavaScript

As we saw in the previous example, the properties of a widget are exposed to a control designer using jQuery. First the properties are mapped from the widget to HTML controls using the refreshUI method, then saved back to the widget during applyChanges.

// …

refreshUI: function () { var controlData = this._propertyEditor.get_control(); // bind widget properties to designer jQuery("#txtName").val(controlData.Name); }, applyChanges: function () { var controlData = this._propertyEditor.get_control(); // bind designer properties back to widget controlData.Name = jQuery("#txtName").val(); }

In the above example, we bind the Name property in our backing widget to a simple textbox that has the id txtName.

Our sample widget is no different. We simply need to make sure that we use the correct methods for checking and setting the different elements using jQuery.

For example, checkboxes and radio buttons are generally inspected using the “attr” method:

jQuery("#Single").attr('checked');

The RadControls, however need to use the client-side API. Fortunately, there is extensive documentation on each and every control in the suite. For example, here is the client-side documentation for the Rad Rating Control: RadRating Client-Side API.

As you can see from the complete implementation, setting the ClientIDMode to Static on the front-end template makes interacting with the RadControl client objects much simpler.

We simply use the $find helper available in the RadControls API to retrieve the client-objects, then set the control value from the Widget properties on load, or save them back to the widget when we are done.

Note: notice that when using the $find helper, you pass in the Client ID, but unlike the jQuery selector, you do not use the # symbol; use $find(“Rating”) not $(“#Rating”).

Type.registerNamespace("SitefinityWebApp.Widgets.SampleWidget");

SitefinityWebApp.Widgets.SampleWidget.SampleWidgetDesigner = function (element) {
    SitefinityWebApp.Widgets.SampleWidget.SampleWidgetDesigner.initializeBase(this, [element]);
}

SitefinityWebApp.Widgets.SampleWidget.SampleWidgetDesigner.prototype = {
    initialize: function () {
        SitefinityWebApp.Widgets.SampleWidget.SampleWidgetDesigner.callBaseMethod(this, 'initialize');
    },
    dispose: function () {
        SitefinityWebApp.Widgets.SampleWidget.SampleWidgetDesigner.callBaseMethod(this, 'dispose');
    },
    refreshUI: function () {
        var controlData = this._propertyEditor.get_control();

        // bind textbox value
        jQuery("#Name").val(controlData.Name);

        // bind dropdown value
        jQuery("#State").val(controlData.State);

        // bind checkbox value
        jQuery("#Single").attr('checked', controlData.Single);

        // bind radio button value
        if (controlData.MyGender == "Female")
            jQuery("#Female").attr('checked', true);
        else
            jQuery("#Male").attr('checked', true);

        // bind date value
        var datePicker = $find("Birthday");
        var birthday = new Date(controlData.Birthday); // cast to date
        datePicker.set_selectedDate(birthday);

        // bind star rating value
        var starRating = $find("Rating");
        starRating.set_value(controlData.Rating);

        // bind Color Picker value
        var colorPicker = $find("HexColor");
        colorPicker.set_selectedColor(controlData.HexColor);
    },
    applyChanges: function () {

        var controlData = this._propertyEditor.get_control();

        // save textbox value
        controlData.Name = jQuery("#Name").val();

        // save dropdown value
        controlData.State = jQuery("#State").val();

        // save checkbox value
        controlData.Single = jQuery("#Single").attr('checked');

                // save radio button value
                if (jQuery("#Female").attr('checked'))
                    controlData.MyGender = "Female";
                else
                    controlData.MyGender = "Male";

        // save date value
        var datePicker = $find("Birthday");
        controlData.Birthday = datePicker.get_selectedDate().format("MM/dd/yyyy");

        // save star rating value
        var starRating = $find("Rating");
        controlData.Rating = starRating.get_value();

        // save color picker value
        var colorPicker = $find("HexColor");
        controlData.HexColor = colorPicker.get_selectedColor();
    }
}

SitefinityWebApp.Widgets.SampleWidget.SampleWidgetDesigner.registerClass('SitefinityWebApp.Widgets.SampleWidget.SampleWidgetDesigner', Telerik.Sitefinity.Web.UI.ControlDesign.ControlDesignerBase);
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

Also notice that we needed to cast the date value both when retrieving it from the Widget and when sending it back, as JavaScript Date objects are formatted differently.

With all these elements in place, we have a complete widget. We’ve made use of many different kinds of HTML elements, as well as some nifty RadControls to easily add some powerful fields to the designer. Here are some screenshots of the widget in action.

Sitefinity-4-Advanced-Custom-Widget-Designer Sitefinity-4-Advanced-Custom-Widget

The complete example is available for download below.

What’s Next

Now that we’ve looked at adding some advanced HTML elements to our widget, our next step is to take a look at integrating and interacting with existing Sitefinity content. This means adding Sitefinity components such as a Page Selector or Image Uploader that work directly with Sitefinity data.

Fortunately, Sitefinity has some helpful Field Elements that we can reuse inside our widgets that can help simplify this process. This will be the topic of our next post.

Until then, take a moment to download and try the example below. If you have any questions, experience any issues, or have any other comments or suggestions, please visit our Developing with Sitefinity discussion forum.

Downloads

12 comments

Leave a comment
  1. Hardy Erlinger Sep 26, 2011
    Thank you, Josh. Very helpful article, as always. Please keep them coming.

    Cheers,
    Hardy
  2. Mario Araya Romero Oct 04, 2011
    Josh  this example is great!

    very helpful ... and i'll be waiting the next article to implement the sitefinity's page selector and image selector!

    thanks
  3. Mario Araya Romero Oct 04, 2011
    Josh, when I click on "save" on the control designer, the input data doesn't save the changes. 

    error:
    http://imageshack.us/photo/my-images/694/errorjoshexample.jpg/
  4. Josh Oct 05, 2011
    Hi Mario, can you debug the project and set a breakpoint at the individual properties and make sure they are firing?

    you can also use Firebug in firefox to set a breakpoint in the designer javascript to inspect the values coming in and out of the widget to make sure they are correct.

    Let me know what you find, thanks!  
  5. Mario Araya Romero Oct 06, 2011
    Hi Josh, (sorry about the double post)

    your code is now working :)
    I don't know what was the issue, I presume was some cached .js or whatever. Also my re-implementation of an user control following your sample is working perfect.

    thanks for the firebug tip!
  6. Stefan Feb 21, 2012
    Can anyone tell me how I would go about adding something like the RadEditor to this type of control?
    The one compatible with Sitefinity, not the one from Telerik.
  7. Josh Feb 23, 2012
    Stefan, I've not tried this but there is an HtmlField control you could use. An example of using Sitefinity fields is available in this article: Selecting Sitefinity 4 Content Inside Widget Designers

    I will explore this myself as well and hopefully create a new post with my results. thank you for your feedback!
  8. Adi Mar 02, 2012
    Hello Josh 
    I've been reading your blog posts regarding Sitefinity widgets, but I have a big problem when trying to send the selected value from a dropdownlist, based on your example. Could you please help me somehow, maybe give me an email address to send you my code...
    Thank you,
    Adi
  9. Josh Mar 02, 2012
    Adi, sure thing, just email josh . morales @telerik .com I'd be happy to take a look
  10. Shady Qaddoura (@ShadyQaddoura) Mar 10, 2012
    Thank you Josh for this example , its greate and gave me what i want excatly :)

    Best Ragards,


  11. El Barto Mar 11, 2013

    Great example. Unfortunately Telerik controls are not compatible setting ClientIDMode="Static". 

    http://www.sitefinity.com/developer-network/forums/developing-with-sitefinity-/2-datepickers-in-control-designer

     

  12. Bo Jan 20, 2014
    I'm confused. Your SampleWidgetDesigner.ascx code behind is SampleWidgetDesigner.ascx.cs but it inherits from SitefinityWebApp.Widgets.SampleWidget.SampleWidgetDesigner1. Additionally, your instructions say to use your code behind (which is SampleWidgetDesigner.acsx.cs) but the code shown is SampleWidgetDesigner1.

    Leave a comment