Creating Custom Form Controls in Sitefinity 4.0

In this tutorial, I’ll show how to build a custom form control for Sitefinity 4.0 from scratch, while showing each step of the process and explaining some of the main gotchas you may encounter on the way. You can download the final project source code here. The tutorial is fairly long and is broken up into 7 parts in order to make it more digestible:

Part 1: Intro to the Form Module

Part 2: Setting up the Project

Part 3: Populate the Control Template (NamedTextBox.ascx)

Part 4: Populate the Client Script File (NamedTextBox.js)

Part 5: Populate the Class File (NamedTextBox.cs)

Part 6: Populate the Client Script File (NamedTextBox.js) – Part Deux

Part 7: Enjoying the Fruit of Your Labour

 

Part 1: Intro to the Form Module

One of the most exciting new modules included in the recently-released Sitefinity 4.0 is the Forms Module. I am only partially saying that because it’s the module I know the best. The Form Builder comes with a bunch of out-of-the-box controls that appear on the right-hand side of the Form Builder screen:

They’re functional, and for most simple forms they do the trick. However, there are a lot of times where you want the form controls to store other information such as dates, often by wrapping a one of the Telerik RadControls, all of are available as part of Sitefinity. Fortunately, Sitefinity 4.0 makes it possible to create custom controls. They’ll show up in the Form Builder, write data to the DB - all that good stuff you’d expect a form control to do.

A quick word on names – Sitefinity 3.X called their controls “Controls” but in Sitefinity 4.0 this has been changed to “Widgets”. I’m going to use “Control/Controls” in this tutorial, but the words Widget and Control can be considered interchangeable.

As part of the Intranet Starter Kit that we at Falafel created for Sitefinity 4.0, I created some custom form controls, and I’ll use one of them today as a basis for this tutorial. This will be a simple control that has a textbox that gets automatically filled in with the name of currently logged-in user’s name. This name can be changed in the textbox before submitting the form. The control will live in a DLL that can be added to the SitefinityWebApp web application. The control is called “NamedTextBox”. Here is what the final product will look like:

By the way, for an excellent introduction to how Sitefinity 4 forms work under the hood, check out the two blog posts that Gabe Sumner did on the subject: Why Sitefinity 4.0’s new Form Builder is awesome and What happens to Form Builder data when forms are modified? They aren’t strict prereqs for this tutorial, but they do give a good set of basics on how form data is stored in the DB.

Before we jump in, I want to give a big thanks to Ivan Dimitrov at Telerik for creating the original version of the code used in this tutorial in this Sitefinity forum post. I borrowed generously from both his code and explanations in creating this tutorial.

 

Part 2: Setting up the Project

In this tutorial we will create a custom control which will live in a separate project. This can then either be added as a project reference to the main SitefinityWebApp project in Visual Studio, or else compiled into a DLL and uploaded into Sitefinity in the “Bin” directory. For this tutorial, the project will be added directly as a project reference in Visual Studio.

1. Create a new Sitefinity 4 project and open up the .csproj file in Visual Studio (called “SitefinityWebApp” by default).

2. Add a new class library project with whatever name you want. In this example, I’m using “CustomFormControls”.

3. Add a project reference from “SitefinityWebApp” to the “CustomFormControls” class library.

4. Add a new class “NamedTextBox.cs”. This will be where the control’s behaviour is specified.

5. Add a “Resources” folder.

6. Add a new template “NamedTextBox.ascx” in the Resources folder (since it’s a class library you may need to copy file from somewhere else). This will be the control’s template, setting the look/feel. Make sure to set the “Build Action” to “Embedded Resource” or else you will be in a world of hurt, as the DLL won’t be able to read the ASCX file when it’s compiled.

7. Add a new JS file “NamedTextBox.js” in the Resources folder. This will be the control’s client-side implementation. Make sure to set the “Build Action” to “Embedded Resource” or else you will be in a world of hurt, as the DLL won’t be able to read the JS file when it’s compiled.

8. Your solution should now have 2 projects and should look like this:

9. Add a reference to the resource JS file in AssemblyInfo.cs:

  1. [assembly: WebResourceAttribute("CustomFormControls.Resources.NamedTextBox.js", "text/javascript")]

10. Add all references needed to create a custom form control to the “CustomFormControls” project (add all the high-lighted references). These will all be used at some point in this tutorial:

 

Part 3: Populate the Control Template (NamedTextBox.ascx)

Similar to control templates for all widgets in Sitefinity 4.0, this control template will set the control’s layout, look and feel. To keep things very simple, let’s just add a single RadTextBox along with the 3 labels (Title, Description and Example) that need to be included in every custom form control. This requires us to register the Telerik.Web.UI assembly, which is where all the RadControls are contained. All of the labels can be set in the control’s properties. Notice the CssClass attribute – those are set to match what Sitefinity uses.

<%@ Control Language="C#" %>
<%@ Register Assembly="Telerik.Web.UI" Namespace="Telerik.Web.UI" TagPrefix="telerik" %>
<asp:Label runat="server" ID="Label1" CssClass="sfTxtLbl" Text="title label"></asp:Label>
<telerik:RadTextBox ID="TextBox1" CssClass="sfTxt" runat="server"></telerik:RadTextBox><br />
<asp:Label runat="server" ID="Label2" CssClass="sfExample" Text="example Label"></asp:Label><br />
<asp:Label runat="server" ID="Label3" CssClass="sfExample" Text="description Label"></asp:Label>

Here’s what the control will look like with all 3 labels set:

 

Part 4: Populate the Client Script File (NamedTextBox.js)

At this point I should explain the concept of front-end vs back-end inside of Sitefinity 4.0. “Front-end” refers to the live published site that the end user will see, while “back-end” refers to the administration (Dashboard) portion that is used to create sites in Sitefinity but is hidden from the user.

 

The purpose of form controls is to be added to a form, which can then be submitted and will save whatever data is entered into it into the database. So for example, you could have an event registration form that took a person’s name and email address. Forms can be submitted from either the front-end (on a published page on the site) or from the back-end (from inside the Dashboard in the Forms module). In order to have a form control’s data save to the database, the client JavaScript file  is needed. So if you left the JS file blank, you’d still be able to save the control’s data from the front-end, but not from the back-end.

However, as an absolute minimum to get the control to work, you need the following, which registers the control’s namespace/class and allows the control to be rendered in the browser. I don’t want to get too deep into the JavaScript before showing main guts of the control, so we’ll come back to the JS file in a while and use just the basic code below for now:

Type.registerNamespace("CustomFormControls");

CustomFormControls.NamedTextBox = function (element) {
    CustomFormControls.NamedTextBox.initializeBase(this, [element]);
}

CustomFormControls.NamedTextBox.registerClass('CustomFormControls.NamedTextBox', 
Telerik.Sitefinity.Web.UI.Fields.FieldControl);

 

Part 5: Populate the Class File (NamedTextBox.cs)

This will be the main file used to create the custom control, and will contain the majority of the code needed for the control. All custom form controls need to do 2 things:

  1. a. Inherit from the “FieldControl” abstract class and,
  2. b. Implement the “IFormFieldControl” interface.

 

Here are the steps to populate the class file:

1. Inherit from “FieldControl” and select implement from abstract class. This will create 4 stub methods. Each method will have a “NotImplementedException()” set by default. For the “TitleControl”, “DescriptionControl”, and “ExampleControl” method, you need to create a reference to the labels that were added on the control template, as well as a public property for each that will appear in the Properties dialog. Leave the “InitializeControls” not implemented method for now.

 #region Public properties (will show up in dialog)
        /// <summary>
        /// Example string
        /// </summary>
        public override string Example { get; set; }

        /// <summary>
        /// Title string
        /// </summary>
        public override string Title { get; set; }

        /// <summary>
        /// Description string
        /// </summary>
        public override string Description { get; set; }
        #endregion
 #region Labels on control template
        /// <summary>
        /// Gets reference to the TitleLabel
        /// </summary>
        protected internal virtual Label TitleLabel
        {
            get
            {
                return this.Container.GetControl<Label>("titleLabel", true);
            }
        }

        /// <summary>
        /// Gets reference to the DescriptionLabel
        /// </summary>
        protected internal virtual Label DescriptionLabel
        {
            get
            {
                return Container.GetControl<Label>("descriptionLabel", true);
            }
        }

        /// <summary>
        /// Gets reference to the ExampleLabel
        /// </summary>
        protected internal virtual Label ExampleLabel
        {
            get
            {
                return this.Container.GetControl<Label>("exampleLabel" this.DisplayMode == FieldDisplayMode.Write);
            }
        }

        /// <summary>
        /// Reference to the TitleControl
        /// </summary>
        protected override WebControl TitleControl
        {
            get
            {
                return this.TitleLabel;
            }
        }

        /// <summary>
        /// Reference to the DescriptionControl
        /// </summary>
        protected override WebControl DescriptionControl
        {
            get
            {
                return this.DescriptionLabel;
            }
        }

        /// <summary>
        /// Gets the reference to the control that represents the example of the field control.
        /// Return null if no such control exists in the template.
        /// </summary>
        /// <value></value>
        protected override WebControl ExampleControl
        {
            get
            {
                return this.ExampleLabel;
            }
        }
        #endregion

2. Change the “LayoutTemplateName” property to point to the control’s template file with the format  “<Project_Name>.Resources.<Control_Name>.ascx”.

In this case it will be:

CustomFormControls.Resources.NamedTextBox.ascx"

3. Add a property that will be a reference to the textbox in the control template:

/// <summary>
/// Gets reference to the TextBox1 control
/// </summary>
protected virtual RadTextBox TextBox1
{
    get
    {
        return this.Container.GetControl<RadTextBox>("TextBox1", true);
    }
}

4. Implement interface “IFormFieldControl” – this interface adds a single property, MetaField.

5. Add a private field to store value of the “MetaField” property:

private IMetaField metaField = null;

 

6. Set the getter and setter for the MetaField property. This will be used to persist data from the control to the DB when the form is submitted:

/// <summary>
/// Gets or sets MetaField property to persist data from control to the DB when form is submitted
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public IMetaField MetaField
{
    get
    {
        if (this.metaField == null)
        {
            this.metaField = this.LoadDefaultMetaField();

            // Add unique field name
            this.metaField.FieldName = "NamedTextBox_" + this.ClientID;
        }

        return this.metaField;
    }

    set
    {
        this.metaField = value;
    }
}

7. Override the “Value” property, which will store the value of the control. This should simply get the value from the Textbox.

/// <summary>
/// Get and set the value of the field. 
/// </summary>
public override object Value
{
    get
    {
        return this.TextBox1.Text;
    }

    set
    {
        this.TextBox1.Text = value.ToString();
    }
}

8. Add a class attribute to define what type of value will be stored to the DB for this form control. In the case of a textbox, we’ll use ShortText.

    [DatabaseMapping(UserFriendlyDataType.ShortText)]
    public class NamedTextBox : FieldControl, IFormFieldControl

9. Add methods to get all client-side script references. The first method, GetScriptDescriptors(), allows the front-end control to pass values for back-end publishing.

The second method, GetScriptReferences(), tells the control which script files it needs to have access to in order to function correctly. Notice that we mention the script file created in Part 4, as well as FieldDisplayMode.js . Also notice that we’re passing the MetaField.FieldName property: this is needed in order for the back-end (client script) to work correctly.

/// <summary>
/// Get list of all scripts used by control
/// </summary>
/// <returns>List of all scripts used by control</returns>
public override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
    var descriptor = new ScriptControlDescriptor(this.GetType().FullName, this.ClientID);
    descriptor.AddComponentProperty("textbox", this.TextBox1.ClientID);
    descriptor.AddProperty("displayMode", this.DisplayMode);    // Pass the display mode value
    // Pass the field name - this is VERY IMPORTANT - if this value isn't passed the back-end 
    // publishing will not work
 descriptor.AddProperty("dataFieldName", this.MetaField.FieldName);
    return new[] { descriptor };
}

/// <summary>
/// Get reference to all scripts
/// </summary>
/// <returns>Reference to all scripts</returns>
public override IEnumerable<System.Web.UI.ScriptReference> GetScriptReferences()
{
    var scripts = new List<ScriptReference>(base.GetScriptReferences())
                    {
                        new ScriptReference("CustomFormControls.Resources.NamedTextBox.js",
  this.GetType().Assembly.FullName),
                        new ScriptReference(
 "Telerik.Sitefinity.Web.UI.Fields.Scripts.FieldDisplayMode.js", 
 "Telerik.Sitefinity"),
                    };
    return scripts;
}

10. Now for the final step in this class: we’ll fill in the “InitializeControls” method. This method is used to implement any custom logic you want for your control.

In this case we are going to first set all the labels, and then use the Sitefinity API to get the name of the currently logged-in user, which will be populated in the textbox. If there is no user logged-in, we’ll just return an empty string.

/// <summary>
/// You have to implement the custom logic you want to have inside IntializeControls()
/// </summary>
protected override void InitializeControls(GenericContainer container)
{
    // Set the label values
    this.ExampleLabel.Text = this.Example;
    this.TitleLabel.Text = this.Title;
    this.DescriptionLabel.Text = this.Description;

    this.TextBox1.Text = GetCurrentSitefinityUser();
}
/// <summary>
/// Get the full name for the currently logged-in Sitefinity user.
/// </summary>
/// <returns>String with full name of current user, or empty string if user not logged in.</returns>
private static string GetCurrentSitefinityUser()
{
    Telerik.Sitefinity.Security.Web.UI.ProfileView pv = 
 new Telerik.Sitefinity.Security.Web.UI.ProfileView();
    Guid currentUserGuid = pv.CurrentUser.UserId;

    if (currentUserGuid != Guid.Empty)
    {
        var userManager = UserManager.GetManager("Default");
        var user = userManager.GetUser(currentUserGuid);
        return user.FirstName + " " + user.LastName;
    }
    else
    {
        return String.Empty;
    }
}

Part 6: Populate the Client Script File (NamedTextBox.js) – Part Deux

Now that we’ve finished all the code that will allow our control to persist data in the front-end, we’re going to modify the client script to allow it persist data in the back-end. To do this we need to add a variable for our textbox (see highlighted text below):

CustomFormControls.NamedTextBox = function (element) {
    this._textbox = null;
    CustomFormControls.NamedTextBox.initializeBase(this, [element]);
}

We then need to create getter/setter functions for the textbox.  These will receive the value the user enters in the textbox in our control, which gets passed by the AddComponentProperty() method we added in our class file. The rest of our JS file from Part 4 can remain the same.

CustomFormControls.NamedTextBox.prototype = {
    /* --------------------------------- set up and tear down --------------------------------- */

    /* --------------------------------- public methods ---------------------------------- */

    // Gets the value of the field control.
    get_value: function () {
        return this._textbox.get_value();
    },

    // Sets the value of the text field control depending on DisplayMode.
    set_value: function (value) {
        this._textbox.set_value(value);

    },

    /* --------------------------------- event handlers ---------------------------------- */

    /* --------------------------------- private methods --------------------------------- */

    /* --------------------------------- properties -------------------------------------- */

    get_textbox: function () {
        return this._textbox;
    },

    set_textbox: function (value) {
        this._textbox = value;
    }
}

 

Part 7: Enjoying the Fruit of Your Labour

Great stuff - the custom control is now ready to rock! Now for the fun part: let’s add it to the Form Builder and see it in action.

1. First off, build your solution and then run the Sitefinity project.

2. Inside the Sitefinity administration side, add config setting for the new control.

a. Go to Admin->Settings->Toolboxes->FormControls->Sections->Common->Tools->Create New

b. Enter in these config settings. Only the first 3 are required for basic settings. The ControlType is the assembly name followed by the control name.

c. Enter “Save”.

3. Go to Content->Forms->Create a form

4. You should now see the custom form control at the bottom of the list of standard, out-of-the-box controls:

5. Drag the new control onto the form. If all is correct, it will render on the form. Notice how the logged-in user’s name gets added automatically to the Named TextBox control.

6. Now to give the control a title. Click the “Edit” button and fill in a value into the “Title” property. This will become the title above your control, as well as the column name that your control’s data will save to in the database.

  

7. Put the form on a page, and then view the page. You should now see the custom control on the page. Press the “Submit” button to submit the form. This will save whatever value is present in the textbox.

8. View the form response you just created. In the Sitefinity administration, go to Content->Forms and click on the “Responses” link on the form you’re using:

9. Now you should see the value you submitted in the textbox. Notice also that the title for the textbox value column is “Logged-In User”. Nifty! You've now successfully saved form data through the front-end.

10. Now to save form data through the back-end. On the same "Responses" screen you're currently on, click the "Create a Response" button on the top-right of the page. This will show you the same form as above, but using Sitefinity's administation instead of the published site. Click the "Submit" button to save the form's data. You've now successfully saved form data through the back-end.

 


Downloads:

That's all for this tutorial. You can download the final project source code here.


comments powered by Disqus