Sitefinity: How to use an HTML editor in a custom widget designer

Sam Rueby Sitefinity, Software Development, Web Development 2 Comments

It constantly shocks me that Telerik makes all of these fancy widgets and form controls but they’re somehow not painfully-easy to implement when creating custom widget designers. Even when using Telerik’s Visual Studio plugin Sitefinity Thunder, it’s not a simple point-and-click to use an HTML editor for a widget property.

Well I need these widgets I create to be as simple as possible for the client, which means using the best form control available for the job. I needed an HTML editor and thanks to this forum post and some debugging on my own, I was able to implement this.

I am going to start from the point where you already have a Sitefinity Widget, and have already created a widget designer for it.

In this example, I’m going to have my Body property be set by the HTML editor.

using System;
using System.Web.UI;

namespace SitefinityWebApp.CustomControls{
	[ Telerik.Sitefinity.Web.UI.ControlDesign.ControlDesigner( typeof( NavigationQuicklinksDesigner ) ) ]
	public partial class NavigationQuicklinks: UserControl {
		public string Heading { get; set; }

		public string Body { get; set; }

		protected void Page_Load( object sender, EventArgs e ) {}
	}
}

 

First, add an HtmlField in the widget designer markup.

<%@ Control %>
<%@ Register Assembly="Telerik.Sitefinity" TagPrefix="sitefinity" Namespace="Telerik.Sitefinity.Web.UI" %>
<%@ Register TagPrefix="sfFields" Namespace="Telerik.Sitefinity.Web.UI.Fields" Assembly="Telerik.Sitefinity, Version=7.2.5317.0, Culture=neutral, PublicKeyToken=b28c218413bdf563" %>

<sitefinity:ResourceLinks ID="resourcesLinks" runat="server">
    <sitefinity:ResourceFile Name="Styles/Ajax.css" />
</sitefinity:ResourceLinks>
<div id="designerLayoutRoot" class="sfContentViews sfSingleContentView" style="max-height: 600px; overflow: auto;">
    <ol>
        <li class="sfFormCtrl">
            <asp:Label runat="server" AssociatedControlID="Heading" CssClass="sfTxtLbl">Heading</asp:Label>
            <asp:TextBox ID="Heading" runat="server" CssClass="sfTxt" />
            <div class="sfExample"></div>
        </li>

        <li class="sfFormCtrl">
            <asp:Label runat="server" AssociatedControlID="Body" CssClass="sfTxtLbl">Body</asp:Label>

            <sfFields:HtmlField
                ID="Body"
                runat="server"
                DisplayMode="Write"
                EditorToolsConfiguration="Standard"
                HtmlFieldEditModes="Design"
                EditorContentFilters="DefaultFilters,RemoveScripts"
                EditorStripFormattingOptions="AllExceptNewLines"
                Height="200"
                Width="350"
                IsToOverrideDialogs="false">
            </sfFields:HtmlField>
            <div class="sfExample"></div>
        </li>

    </ol>
</div>

Go ahead and configure the HtmlField with whatever properties that fit your needs.

Next, go into the widget designer code-behind and add the highlighted lines below.

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

namespace SitefinityWebApp.CustomControls{
	/// <summary>
	/// Represents a designer for the <typeparamref name="SitefinityWebApp.CustomControls.ContentWidget"/> widget
	/// </summary>
	public class NavigationQuicklinksDesigner: ControlDesignerBase {
		#region Properties

		/// <summary>
		/// Obsolete. Use LayoutTemplatePath instead.
		/// </summary>
		protected override string LayoutTemplateName { get { return string.Empty; } }

		/// <summary>
		/// Gets the layout template's relative or virtual path.
		/// </summary>
		public override string LayoutTemplatePath {
			get {
				if( string.IsNullOrEmpty( base.LayoutTemplatePath ) )
					return layoutTemplatePath;
				return base.LayoutTemplatePath;
			}
			set { base.LayoutTemplatePath = value; }
		}

		protected override HtmlTextWriterTag TagKey { get { return HtmlTextWriterTag.Div; } }

		#endregion

		#region Control references

		/// <summary>
		/// Gets the control that is bound to the Heading property
		/// </summary>
		protected virtual Control Heading { get { return Container.GetControl<Control>( "Heading", true ); } }

		/// <summary>
		/// Gets the control that is bound to the Body property
		/// </summary>
		protected virtual HtmlField Body { get { return Container.GetControl<HtmlField>( "Body", true ); } }

	
		#endregion

		#region Methods

		protected override void InitializeControls( Telerik.Sitefinity.Web.UI.GenericContainer container ) {
			// Place your initialization logic here
		}

		#endregion

		#region IScriptControl implementation

		/// <summary>
		/// Gets a collection of script descriptors that represent ECMAScript (JavaScript) client components.
		/// </summary>
		public override IEnumerable<ScriptDescriptor> GetScriptDescriptors() {
			var scriptDescriptors = new List<ScriptDescriptor>( base.GetScriptDescriptors() );
			var descriptor = (ScriptControlDescriptor)scriptDescriptors.Last();

			descriptor.AddElementProperty( "heading", Heading.ClientID );
			descriptor.AddElementProperty( "body", Body.ClientID );

			return scriptDescriptors;
		}

		
		/// <summary>
		/// Gets a collection of ScriptReference objects that define script resources that the control requires.
		/// </summary>
		public override IEnumerable<ScriptReference> GetScriptReferences() {
			var scripts = new List<ScriptReference>( base.GetScriptReferences() );
			scripts.Add( new ScriptReference( scriptReference ) );
			return scripts;
		}

		#endregion

		#region Private members & constants

		public static readonly string layoutTemplatePath = "~/CustomControls/NavigationQuicklinksDesigner.ascx";
		public const string scriptReference = "~/CustomControls/NavigationQuicklinksDesigner.js";

		#endregion
	}
}

 

Lastly, the most error-prone part, add the highlighted lines in the JavaScript-behind for the widget deisgner.

Type.registerNamespace( "SitefinityWebApp.CustomControls" );

SitefinityWebApp.CustomControls.NavigationQuicklinksDesigner = function( element ) {
    /* Initialize Heading fields */
    this._heading = null;

    /* Initialize Body fields */
    this._body = null;

    /* Calls the base constructor */
    SitefinityWebApp.CustomControls.NavigationQuicklinksDesigner.initializeBase( this, [element] );
};
SitefinityWebApp.CustomControls.NavigationQuicklinksDesigner.prototype = {
    /* --------------------------------- set up and tear down --------------------------------- */
    initialize: function() {
        /* Here you can attach to events or do other initialization */
        SitefinityWebApp.CustomControls.MeatWeEat.NavigationQuicklinksDesigner.callBaseMethod( this, 'initialize' );
    },
    dispose: function() {
        /* this is the place to unbind/dispose the event handlers created in the initialize method */
        SitefinityWebApp.CustomControls.MeatWeEat.NavigationQuicklinksDesigner.callBaseMethod( this, 'dispose' );
    },

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

    findElement: function( id ) {
        var result = jQuery( this.get_element() ).find( "#" + id ).get( 0 );
        return result;
    },

    /* Called when the designer window gets opened and here is place to "bind" your designer to the control properties */
    refreshUI: function() {
        var controlData = this._propertyEditor.get_control(); /* JavaScript clone of your control - all the control properties will be properties of the controlData too */

        /* RefreshUI Heading */
        jQuery( this.get_heading() ).val( controlData.Heading );

        /* RefreshUI Body */
        var htmlText = controlData.Body ? controlData.Body : "";
        this.get_body().control.set_value(htmlText);
    },

    /* Called when the "Save" button is clicked. Here you can transfer the settings from the designer to the control */
    applyChanges: function() {
        var controlData = this._propertyEditor.get_control();
        /* ApplyChanges Heading */
        controlData.Heading = jQuery( this.get_heading() ).val();

        /* ApplyChanges Description */
        var htmlText = this.get_body().control.get_value();
        controlData.Body = htmlText;
    },

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

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

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

    /* Heading properties */
    get_heading: function() { return this._heading; },
    set_heading: function( value ) { this._heading = value; },

    /* Body properties */
    get_body: function() { return this._body; },
    set_body: function( value ) { this._body = value; }
};
SitefinityWebApp.CustomControls.NavigationQuicklinksDesigner.registerClass( 'SitefinityWebApp.CustomControls.ContentWidgetDesigner', Telerik.Sitefinity.Web.UI.ControlDesign.ControlDesignerBase );

That’s all the code you should need. Now if you compile Sitefinity and go into your backend, drag your widget onto the page, choose properties and see your HTML editor in action.

Animated GIF showing the result of adding an HTML editor to  the custom widget designer