Creating Advanced Sitefinity 4 Widget Control Designers

Creating Advanced Sitefinity 4 Widget Control Designers

Posted on September 20, 2011 0 Comments

The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.

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

progress-logo

The Progress Team

View all posts from The Progress Team on the Progress blog. Connect with us about all things application development and deployment, data integration and digital business.

Comments

Comments are disabled in preview mode.
Topics

Sitefinity Training and Certification Now Available.

Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.

Learn More
Latest Stories
in Your Inbox

Subscribe to get all the news, info and tutorials you need to build better business apps and sites

Loading animation