Fabio's blog

marketing | technology

Archive for December 2010

Using EPiServer dynamic content to create a widget manager

leave a comment »

Dynamic content, a feature introduced in EPiServer 5, is very useful as it allows CMS users to effectively inject your code in the WYSIWYG editor. Unfortunately, there is no way of using this feature in a more controlled manner… unless you fancy rolling out your custom property to do that.

What I wanted to achieve was to use dynamic content plug-ins like WordPress widgets or Umbraco macros; for example, when you have an area in your template where CMS users can add/edit/delete/sort dynamic content plug-ins, each one with its property setters. So after taking a look at the way the dynamic content dialogue works, I thought this would be easy to do with a simple custom property and some javascript / jquery.

The custom property

A simple override of the PropertyXhtmlString, using an .ascx control for the editing experience.

  [Serializable]
  [PageDefinitionTypePlugIn(DisplayName = "Features catalogue")]
  public class PropertyWidgetContainer : PropertyXhtmlString
  {
    public override IPropertyControl CreatePropertyControl()
    {
      return (IPropertyControl)BuildManager.CreateInstanceFromVirtualPath("~/templates/controls/features/widgetcontainer.ascx", typeof(WidgetContainer));
    }
  }

The editing control (code behind)

This is the default code which will render the property to the editor.

  public partial class WidgetContainer : UserControlBase, IPropertyControl
  {
    public WidgetContainer()
    {
      Enabled = true;
    }

    public void SetupControl()
    {
      if (RenderType == RenderType.Edit)
      {
        EpiContext = "{ \"id\": \"" + CurrentPage.PageLink.ID + "_" + CurrentPage.PageLink.WorkID + "\", \"parentId\": \"" + CurrentPage.ParentLink.ID + "\", \"pageFolderId\": \"" + CurrentPage.Property["PageFolderID"].Value + "\", \"epslanguage\": \"" + CurrentPage.LanguageBranch + "\" }";
        dataContainer.Text = (string)PropertyData.Value;
      }
    }

    public void ApplyChanges()
    {
      PropertyData.Value = dataContainer.Text;
    }

    public bool DisplayEditUI
    {
      get { return PropertyData.DisplayEditUI; }
    }

    public PropertyData PropertyData { get; set; }
    public PropertyDataCollection Properties { get; set; }
    public RenderType RenderType { get; set; }

    public TableRowLayout RowLayout
    {
      get { return TableRowLayout.Default; }
    }

    public string ValidationGroup { get; set; }
    public bool Enabled { get; set; }
    public string EpiContext { get; set; }
  }

The editing control

So far so good, a quick test of the basics is to have a textarea to input the source code of any dynamic content…

<asp:textbox id="dataContainer" textmode="MultiLine" runat="server" />

So I can run this, enter something like the html source generated by a dynamic content plug-in and save…

<span classid="b30218a7-77fc-43dd-a844-81935aa9b35e" dynamicclass="Simple text" state="PHA+SnVzdCBzb21lIHRleHQgb24gdGhlIHJpZ2h0PC9wPg==|" disabled="disabled" contentEditable="false">{DynamicContent:Simple text}</span>

Now, if I can find a way of translating what’s in this textarea to a friendly interface, we’re basically there. JQuery to the rescue…

<asp:textbox id="dataContainer" textmode="MultiLine" runat="server" />
<div class="epi-buttonDefault">
  <span class="epi-cmsButton"><input class="epi-cmsButton-text epi-cmsButton-tools epi-cmsButton-Add" id="addplugin" type="button" /></span>
</div>
<table class="epi-default" id="tableContainer">
	<thead>
		<tr class="header">
			<th>Module</th>
			<th>Actions</th>
		</tr>
	</thead>
	<tbody>	
	</tbody>
</table>
<script type="text/javascript" src="/js/lib.min.js"></script>
<script type="text/javascript">
  var tpl = '<tr class="pluginRow">' +
                    '<td class="plugin">%PLUGIN%</td>' +
                    '<td><span class="epi-cmsButton"><input class="epi-cmsButton-tools epi-cmsButton-Delete" type="button"></span>' +
                    '<span class="epi-cmsButton"><input class="epi-cmsButton-tools epi-cmsButton-Edit" type="button"></span></td>' +
                '</tr>';

  var context = <%= EpiContext %>, data, table;
  jQuery(function () {
    var widgetMgr = new widgets.manager($('#<%= dataContainer.ClientID %>'), $('#<%= tableContainer.ClientID %>'), tpl);
    if (widgetMgr.data.val() != '') {
      var el = $('<div />').append(widgetMgr.data.val());
      $('.dynamiccontent', el).each(function() {
        widgetMgr.addItem({ plugin: $('<div />').append($(this)).html() });
      });
    }
    $('#<%= addplugin.ClientID %>').click(function (evt) {
      var args = { plugin: '', index: -1, instance: widgetMgr };
      EPi.CreateDialog(EPi.ResolveUrlFromUI('Editor/Dialogs/DynamicContent.aspx') + '?' +  $.param(context), widgetMgr.onDialogClosed, args, null, { width: 470, height: 380 });
      evt.preventDefault();
    });
  });
</script>

The widgetManager javascript class takes care of doing most of the hard work…

/**************************************************
* Widget manager for EPiServer, requires JQuery and the TableDnD plug-in for JQuery (for sorting)
***************************************************/
	widgets = {};
	widgets.manager = function (d, t, k) {
	  this.init(d, t, k);
	};
	widgets.manager.prototype = {
	  constructor: widgets.manager,
	  init: function (d, t, k) {
	    this.data = d.hide();
	    this.table = t.hide();
	    this.tpl = k;
	  },
	  data: null,
	  table: null,
	  tpl: '',
	  setItems: function () {
	    this.data.empty();
	    var el = $('<div />');
	    $('.plugin', this.table).each(function () {
	      el.append($(this).html());
	    });
	    this.data.val(el.html());
	    if (this.data.val() === '') {
	      this.table.hide();
	    } else {
	      this.table.show();
	      this.bindTableEvents();
	    }
	  },
	  addItem: function (arguments) {
	    $('tbody', this.table).append($(this.tpl.replace('%PLUGIN%', arguments.plugin)));
	    this.setItems();
	  },
	  editItem: function (arguments) {
	    var tblrows = $('.pluginRow', this.table);
	    $(tblrows[arguments.index]).html($(this.tpl.replace('%PLUGIN%', arguments.plugin)).html());
	    this.setItems();
	  },
	  bindTableEvents: function () {
	    var instance = this;
	    var tblrows = $('.pluginRow', instance.table);
	    $('.epi-cmsButton-Delete', tblrows).unbind('click');
	    $('.epi-cmsButton-Delete', tblrows).click(function (evt) {
	      if (confirm("Are you sure?")) {
	        var tr = $(this).parents("tr:first");
	        tr.remove();
	        instance.setItems();
	      }
	      evt.preventDefault();
	    });
	    $('.epi-cmsButton-Edit', tblrows).unbind('click');
	    $('.epi-cmsButton-Edit', tblrows).click(function (evt) {
	      var tr = $(this).parents("tr:first");
	      var selectedNode = $('.dynamiccontent', tr);
	      var formString = '<html xmlns="http://www.w3.org/1999/xhtml"><head><title></title></head><body onload="document.forms[0].submit()">' +
                    '<form action="' + EPi.ResolveUrlFromUI('Editor/Dialogs/DynamicContent.aspx') + '?' + $.param(context) + '" method="post">' +
                    '<input name="state" type="hidden" value="' + selectedNode.attr("state").replace(/"/g, '&quot;') + '" />' +
                    '<input name="hash" type="hidden" value="' + selectedNode.attr("hash").replace(/"/g, '&quot;') + '" />' +
                    '<input name="dynamicclass" type="hidden" value="' + selectedNode.attr("dynamicclass").replace(/"/g, '&quot;') + '" />' +
                    '</form></body></html>';
	      var args = { plugin: $('td:first', tr).html(), index: tblrows.index(tr), instance: instance };
	      var dialog = EPi.CreateDialog(EPi.ResolveUrlFromUI('Editor/Dialogs/DynamicContent.aspx') + '?' + $.param(context), instance.onDialogClosed, args, null, { width: 470, height: 380 });
	      dialog._dialog.document.write(formString);
	      dialog._dialog.document.close();
	      evt.preventDefault();
	    });
	    instance.table.tableDnD({
	      onDrop: function (table, row) {
	        instance.setItems();
	      }
	    });
	  },
	  onDialogClosed: function (returnObject, onCompleteArguments) {
	    if (!returnObject) {
	      return;
	    }
	    if (onCompleteArguments.index === -1) {
	      onCompleteArguments.plugin = returnObject;
	      onCompleteArguments.instance.addItem(onCompleteArguments);
	    } else {
	      onCompleteArguments.plugin = returnObject;
	      onCompleteArguments.instance.editItem(onCompleteArguments);
	    }
	  }
	};

Excellent, now I can add widgets…

… sort them with drag’n’drop, delete them, change their settings…

… and so on.

When I view the saved version, the dynamic content plugins render as I expect them to do.

Limitations

The dynamic content factory in the current EPiServer version does not allow you to provide an alternative implementation – one, for instance, where you can organise your dynamic content by user role or area, so limiting which plug-ins can be used by who or where.

Other stuff

I’ve been using the labs DCPlugin to make creation of plugins easier (http://labs.episerver.com/en/Blogs/Allan/Dates/2009/2/Turn-your-User-Controls-into-Dynamic-Content/) – I’ve recompiled it to address a Unicode character bug.

Written by Fabio

December 17, 2010 at 3:50 pm

Posted in .NET, EPiServer

Follow

Get every new post delivered to your Inbox.