A User Control by Any Other Name

I have a confession to make, when Ektron announced their PageBuilder functionality, with the drag & drop widgets, I wasn’t excited. I was happy building pages the way I had been, and historically, a good bit of my time was spent fixing pages that my content editors had messed up, because they didn’t listen to what I had told them. So why would I be interested in a technology that gave them more control?

Then I watched Jason Arden’s Lunch and Learn webinar, that talked about PageBuilder, and the widgets, and I finally saw the potential. (When I built my first widget, dragged it onto a page, and it actually worked, I think I may have even done a happy dance.)

Fast forward a few months, I had built a widget to handle a specific problem (I don’t remember what it was), and then found that I needed the same functionality, but I couldn’t use PageBuilder in that circumstance. Being the innocent and naive soul that I was back then, I didn’t know that widgets where just dressed up user controls. (Sure, they had the same extension, but what does that mean anyway?) So I copied the code into a standard user control, removed all of the widget specific portions, and had two version with the same functionality. Two versions where I had to make changes, when they were inevitably needed. Not ideal.

I have recently learned that you can use Widgets just like you would a normal user control. Since code re-use is a goal of all back-end developers, this was an exciting revelation for me, and it just takes a few simple changes to your widget code.

NOTE: I am not going to go into depth about how to build widgets, or all of the components of one. James Stout ( @eGandalf) has written some outstanding blog posts, walking you through widget development http://www.ektron.com/blog/egandalf/. )

The examples and screenshots for this post were all built on the 8.5 CMS platform, but you could easily do this on 8.0 as well.

 

Let’s jump in.

For this example, I will be using the HelloWorld widget. This widget is standard in the Ektron 8.0+ CMS installs, and is about as simple as widgets get, so it will be easy to explain the changes.

First, let’s take a look at the default HelloWorld widget code.

HelloWorld.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="HelloWorld.ascx.cs" Inherits="widgets_HelloWorld" %>
    <asp:MultiView ID="ViewSet" runat="server" ActiveViewIndex="0">
        <asp:View ID="View" runat="server">
            <!-- You Need To Do ..............................  -->
            <asp:Label ID="OutputLabel" runat="server"></asp:Label>
            <!-- End To Do ..............................  -->
        </asp:View>
        <asp:View ID="Edit" runat="server">
            <div id="<%=ClientID%>_edit">
                 <!-- You Need To Do ..............................  -->
                 <asp:TextBox ID="HelloTextBox" runat="server" Style="width: 95%"> </asp:TextBox>
                  <!-- End To Do ..............................  -->
                 <asp:Button ID="CancelButton" runat="server" Text="Cancel" OnClick="CancelButton_Click" /> &nbsp;&nbsp;
                <asp:Button ID="SaveButton" runat="server" Text="Save" OnClick="SaveButton_Click" />
            </div>
        </asp:View>
    </asp:MultiView>

Not very complicated, and if you have looked at any of the widget code, this should be pretty familiar. We have the standard <%@ Control declaration statement at the top, and Asp Multiview control to show either the Normal view with an asp:Label for output, or the Edit view, with a simple form for updating and saving the widget properties.

 

In the code behind file, HelloWorld.ascx.cs

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using Ektron.Cms.Widget;
using Ektron.Cms;
using Ektron.Cms.API;
using Ektron.Cms.Common;
using Ektron.Cms.PageBuilder;
using System.Configuration;

public partial class widgets_HelloWorld : System.Web.UI.UserControl, IWidget
{
    #region properties
    private string _HelloString;
   [WidgetDataMember("Hello World")]
    public string HelloString { get { return _HelloString; } set { _HelloString = value; } }
    #endregion

    IWidgetHost _host;
    protected void Page_Init(object sender, EventArgs e)
    {
        string sitepath = new CommonApi().SitePath;
        JS.RegisterJSInclude(this, JS.ManagedScript.EktronJS);
        JS.RegisterJSInclude(this, JS.ManagedScript.EktronModalJS);
        Css.RegisterCss(this, Css.ManagedStyleSheet.EktronModalCss);
        _host = Ektron.Cms.Widget.WidgetHost.GetHost(this);
        _host.Title = "Hello World Widget";
        _host.Edit += new EditDelegate(EditEvent);
        _host.Maximize += new MaximizeDelegate(delegate() { Visible = true; });
        _host.Minimize += new MinimizeDelegate(delegate() { Visible = false; });
        _host.Create += new CreateDelegate(delegate() { EditEvent(""); });
        PreRender += new EventHandler(delegate(object PreRenderSender, EventArgs Evt) { SetOutput(); });
        string myPath = string.Empty;
        if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["ek_helpDomainPrefix"]))
        {
            string helpDomain = ConfigurationManager.AppSettings["ek_helpDomainPrefix"];
            if ((helpDomain.IndexOf("[ek_cmsversion]") > 1))
            {
                myPath = helpDomain.Replace("[ek_cmsversion]", new CommonApi().RequestInformationRef.Version);
            }
            else
            {
                myPath = ConfigurationManager.AppSettings["ek_helpDomainPrefix"];
            }
        }
        else
        {
            myPath = sitepath + "Workarea/help";
        }
        _host.HelpFile = myPath + "/Widget Chapter/Hello World/Creating the Hello World Widget.htm";
        ViewSet.SetActiveView(View);
    }

    void EditEvent(string settings)
    {
        HelloTextBox.Text = HelloString;
        ViewSet.SetActiveView(Edit);
    }

    protected void SaveButton_Click(object sender, EventArgs e)
    {
        HelloString = HelloTextBox.Text;
        _host.SaveWidgetDataMembers();
        ViewSet.SetActiveView(View);
    }

    protected void SetOutput()
    {
        OutputLabel.Text = HelloString;
    }

    protected void CancelButton_Click(object sender, EventArgs e)
    {
        ViewSet.SetActiveView(View);
    }
}

In this file, we see the normal using statements at the top; the widget property declarations, in this case just the one for HelloString (I did say this was a simple widget); the Page_Init function, that sets the widget properties, registers some javascript and css  files, and creates the delegates for the IWidget _host interface.

After that, is the EditEvent function, that populates the fields in the Edit view, which allows you to change the text that is going to be displayed on the screen. The SaveButton_Click function, which saves the values from the Edit form to the widget properties. The SetOutput function, which generally is used to build or populate the actual widget View output. And the CancelButton_Click function, which cancels the Edit mode.

In order to also use this widget as a standard user control, we need to make a few modifications.

I created a new user control file in the widgets directory: jaytemHelloWorld.ascx, and added the following code from the default widget.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="jaytemHelloWorld.ascx.cs" Inherits="widgets_jaytemHelloWorld" %>

<%-- Used to initialize a Script Manager is one does not exist --%>
<asp:PlaceHolder ID="phScriptManager" runat="server" />

<asp:MultiView ID="ViewSet" runat="server" ActiveViewIndex="0">
     <asp:View ID="View" runat="server">
          <asp:Label ID="lblOutput" runat="server" />
     </asp:View>
     <asp:View ID="Edit" runat="server">
          <div id="<%=ClientID %>_edit">
               <asp:Label ID="lblHelloText" runat="server" AssociatedControlID="txtHelloTextBox" Text="Text to Display" />
              <asp:TextBox ID="txtHelloTextBox" runat="server" style="width: 95%" />
              <div>
                    <asp:Button ID="CancelButton" runat="server" Text="Cancel" OnClick="CancelButton_Click" />
                    <asp:Button ID="SaveButton" runat="server" Text="Save" OnClick="SaveButton_Click" />
              </div>
          </div>
     </asp:View>
</asp:MultiView>

 

The only real difference between the two is the addition of the Asp PlaceHolder control, just below the @Control definition. This will be used to add and initialize a Script Manager, if we are using this widget as a user control.

And the code behind: jaytemHelloWorld.ascx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Ektron.Cms.Widget;
using Ektron.Cms;
using Ektron.Cms.API;
using Ektron.Cms.Common;
using Ektron.Cms.PageBuilder;

public partial class widgets_jaytemHelloWorld : System.Web.UI.UserControl, IWidget
{
	#region Properties

	private string _HelloString;

	[WidgetDataMember("Hello World")]
	public string HelloString
	{
		get { return _HelloString; }
		set { _HelloString = value; }
	}

	private IWidgetHost _host;

	#endregion

	#region Constructor

	public widgets_jaytemHelloWorld()
	{
		_host = null;
		HelloString = String.Empty;
	}

	#endregion

	#region Page Events

	protected void Page_Init(object sender, EventArgs e)
    {
		string sitepath = new CommonApi().SitePath;

		//Tries to populate host if PageHost exists
		_host = Ektron.Cms.Widget.WidgetHost.GetHost(this);

		//Keeps null reference from occuring when using as a standard user control
		if (_host != null)
		{
			_host.Title = "Jaytem's Hello World Widget";
			_host.Edit += new EditDelegate(EditEvent);
			_host.Maximize += new MaximizeDelegate(delegate() { Visible = true; });
			_host.Minimize += new MinimizeDelegate(delegate() { Visible = false; });
			_host.Create += new CreateDelegate(delegate() { EditEvent(""); });
			_host.ExpandOptions = Expandable.ExpandOnEdit;
		}

		ViewSet.SetActiveView(View);

		//If the Scriptmanager is null, then instantiate and add to placeholder control
		if (ScriptManager.GetCurrent(this.Page) == null)
		{
			ScriptManager sManager = new ScriptManager();
			sManager.ID = "sManager_" + DateTime.Now.Ticks;
			phScriptManager.Controls.AddAt(0, sManager);
		}
	}

	protected void Page_PreRender(object sender, EventArgs e)
	{
		if (ViewSet.GetActiveView() != View)
			return;

		//if HelloString is passed in the properties, use that
		if (!string.IsNullOrEmpty(HelloString))
		{
			lblOutput.Text = HelloString;
		}
	}

	#endregion

	#region Event Handlers

	protected void SaveButton_Click(object sender, EventArgs e)
	{
		HelloString = txtHelloTextBox.Text.Trim();
		_host.SaveWidgetDataMembers();
		ViewSet.SetActiveView(View);
	}

	protected void CancelButton_Click(object sender, EventArgs e)
	{
		ViewSet.SetActiveView(View);
	}

	#endregion

	#region Methods

	void EditEvent(string settings)
	{
		txtHelloTextBox.Text = HelloString;
		ViewSet.SetActiveView(Edit);
	}

	private void SetOutput()
	{
		lblOutput.Text = HelloString;
	}

	#endregion
}

The code is very similar, with a few important differences (Plus I like to organize my code a bit differently).

Just below the Properties region, I have a Constructor. The Constructor allows us to instantiate an instance of the widget user control, and set the properties without needing the PageBuilder functionality. The constructor also sets some default widget property values, which is very important.

I cleaned up the Page_Init function a bit, getting rid of some of the functionality that this widget really didn’t need (although it would be very useful in a more complex widget). I also added a check for the ScriptManager, checking to see if it is null or not (null means that this widget is being used outside of the PageBuilder environment). If the ScriptManager is null, then we need to create a new instance of it, and add it to the page. This is where the Asp:PlaceHolder control that we added to the .ascx page comes in.

After the Page_Init function, we have the Page_PreRender event function. The Page_PreRender event happens after the Page_Load, but before the page is rendered to the screen. When using widgets in the PageBuilder environment, the PreRender event handler is set using a delegate, and referencing the SetOutput function. In this case, we use the event when using this widget as a user control, we have to do it ourselves.

Everything else is exactly the same.

Let’s see it in Action

As a Widget

First, let’s start with using this widget as a widget. I have a very simple PageBuilder template, with a single drop-zone. My new widget is in the widgets directory, and I have added it to the Widgets list, and to the template in the WorkArea Settings.

I creates a new PB page, using my new template, and add the Hello World widgets by dragging and dropping the widgets into the drop-zone.

 

Next, I will edit the new widget, and update the text to be displayed.

I click save, and then Publish the PB page, so we end up with both widgets displaying on the page.

 

 

As a User Control

There are two ways that you can use your new widget as a user control: by adding a control to the .aspx page, and passing in the parameters as attributes; or by adding the user control to the page programmatically.

For this demonstration, I have very simple .aspx page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="widget_uc.aspx.cs" Inherits="test_widget_uc" %>

<%-- Register the widget on the page, to use as a normal user control --%>
<%@ Register Src="~/widgets/jaytemHelloWorld.ascx" TagName="HelloWorld" TagPrefix="jtm" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
     <head runat="server">
          <title></title>
     </head>
     <body>
          <form id="form1" runat="server">
               <div>
                    <%-- Add the user control to the page --%>
                    <jtm:HelloWorld ID="jtmHW" runat="server" HelloString="Hello World. Broadcasting from the widget user control" />

                    <%-- Add an asp:PlaceHolder control, to add the widget user control to the page programmatically --%>
                    <p>
                         <asp:PlaceHolder ID="phControls" runat="server" />
                    </p>
               </div>
          </form>
     </body>
</html>

You first need to register the widget on the page. This needs to be done for both methods, or it won’t work.  Add a new <%@ Register  declaration, at the top of the page, but below the <% @ Page declaration. Put the path to the widget in the Src attribute, and give it a TagName and TagPrefix values.

Next, add the user control to the page in the markup, setting a new HelloString value. When you run the page, this is what you see:

 

Simple, right?

If you wanted to add the user control programmatically, it’s almost as simple. Here is the code behind file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class test_widget_uc : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
		ASP.widgets_jaytemhelloworld_ascx HelloWorld = new ASP.widgets_jaytemhelloworld_ascx();
		HelloWorld.HelloString = "So Long, and Thanks for all the Fish";
		phControls.Controls.Add(HelloWorld);
    }
}

In the Page_Load Event function, I am instantiating a new instance of the widget user control. Then I am setting the string that I want to output, and adding the control to an asp:PlaceHolder control that I have on the .aspx page. I like using PlaceHolder controls in this instance, because I can control the placement of the new user control on the page.

When you refresh the page, the second user control is added.

 

So there you have it. Having the option to use your widgets as standard user controls opens up some possibilities on your site, and is a great example of code reuse. You don’t have to create a new user control if you already have the functionality in a widget. Just use the widget.

Happy Coding.

2 thoughts on “A User Control by Any Other Name”

  1. Great post!

    In your new widget control, it seems like the SetOutput() function is never called. I think it would be better to call it from the pre_render function, and move the null check on HelloString to the SetOutput function.

  2. Thanks. I am glad you found it interesting. You are absolutely correct, the SetOutput() function isn’t used, and should be. That was an oversight on my part. Good catch.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.