Thursday, 23 September 2010

Using Dynamic Content Controls for Composer Sort Of Functionality

During a recent project review I found myself looking at wireframes which looked much like the end of an intense Jenga session. There were lots of content blocks in different formations on what were meant to be single templates. Focusing on 1 template with 3 variations I queried how the layout of the page body could differ so dramatically and how each right hand column had a different selection of content elements.

“But surely Editors can just drag and drop content blocks onto a page? After all, we live in a world where you can buy 3D TVs and squeeze zits for points on the latest Smartphone App.” Came the perplexed and slightly paraphrased response.

Ah, you must be thinking of EPiServer Composer, I scoffed. Sadly, this was an EPiServer CMS 6 build and Composer was not in the project budget.

I now found myself in a wireframe prison where the only way out was to trick the guard by pretending to be Mr EPiServer ConPoser. (TeeHee)

So, what were my options?


The first option which we used in a previous build was to create a number of 1, 2 and 3 column splitter Page Type properties that could be enabled and populated, or disabled, per page. This approach was okay but it did give with one hand and flick annoyingly on the end of the nose with the other. Going down this road would limit editors to the number, variation and order of elements on page.

The second option was to create a bunch of Dynamic Content Controls and allow editors to add them into a Page Area via the TinyMCE Editor Plug-in utility. I decided to investigate this approach as it didn’t have the number, variation and order limitations.

The first task was to develop two and three column splitter controls that could be added into the page body. I also created a one column splitter control but as colleagues gleefully pointed out that was, well, pretty stupid. I’ve renamed that to a Row Splitter.

Example: Two Column Splitter Control
public class TwoColumnSplitter : IDynamicContent
    {
        private PropertyDataCollection _props = new EPiServer.Core.PropertyDataCollection();

        public TwoColumnSplitter()
        {
            _props.Add("Column1TitleText", new PropertyString(""));
            _props.Add("Column1BodyText", new PropertyXhtmlString(""));
            _props.Add("Column2TitleText", new PropertyString(""));
            _props.Add("Column2BodyText", new PropertyXhtmlString(""));
        }

        public string Column1TitleText
        {
            get
            {
                if (_props["Column1TitleText"].Value != null)
                {
                    return _props["Column1TitleText"].Value.ToString();
                }

                return null;
            }
            set
            {
                _props["Column1TitleText"].Value = value;
            }
        }

        public string Column1BodyText
        {
            get
            {
                if (_props["Column1BodyText"].Value != null)
                {
                    return _props["Column1BodyText"].Value.ToString();
                }

                return null;
            }
            set
            {
                _props["Column1BodyText"].Value = value;
            }
        }

        public string Column2TitleText
        {
            get
            {
                if (_props["Column2TitleText"].Value != null)
                {
                    return _props["Column2TitleText"].Value.ToString();
                }

                return null;
            }
            set
            {
                _props["Column2TitleText"].Value = value;
            }
        }

        public string Column2BodyText
        {
            get
            {
                if (_props["Column2BodyText"].Value != null)
                {
                    return _props["Column2BodyText"].Value.ToString();
                }

                return null;
            }
            set
            {
                _props["Column2BodyText"].Value = value;
            }
        }

        #region IDynamicContent Members

        public System.Web.UI.Control GetControl(PageBase hostPage)
        {
            TwoColumnSplitterControl control = (TwoColumnSplitterControl)hostPage.LoadControl("~/DynamicContent/TwoColumnSplitterControl.ascx");
            control.Column1TitleText = this.Column1TitleText;
            control.Column1BodyText = this.Column1BodyText;
            control.Column2TitleText = this.Column2TitleText;
            control.Column2BodyText = this.Column2BodyText;
            return control;
        }

        public PropertyDataCollection Properties
        {
            get { return _props; }
        }

        public string Render(PageBase hostPage)
        {
            throw new NotImplementedException();
        }

        public bool RendersWithControl
        {
            get { return true; }
        }

        public string State
        {
            get
            {
                StringBuilder state = new StringBuilder();

                state.Append(Column1TitleText);
                state.Append("|");
                state.Append(Column1BodyText);
                state.Append("|");
                state.Append(Column2TitleText);
                state.Append("|");
                state.Append(Column2BodyText);
                state.Append("|");
                return state.ToString();
            }
            set
            {
                if (!string.IsNullOrEmpty(value))
                {
                    string[] state = value.Split('|');
                    Column1TitleText = state[0];
                    Column1BodyText = state[1];
                    Column2TitleText = state[2];                  
                }
            }
        }

        #endregion
    }

Each of these controls had their own Rending Control.
I didn’t opt to create a custom Editor Control as the EPiServer standard interface sufficed.

I then added a reference to them in episerver.config and hey presto – they were available to add via the RTE.

Rich Text Editor
Plug-in Inteface
HTML Output
Editors can now add a variety of page body layout and content items to their hearts content.
The one drawback was that the RTE by default adds p and span tags around the Dynamic Controls which could cause a problem in displaying these items together.

To solve this, I created a Custom Property with its own edit control and removed the redundant markup before save. I used the HTML Agility Pack and a bit of code snooping in Anders Hattestad blog post about Dynamic Content Preview.
[Serializable, PageDefinitionTypePlugIn]
    public class DynamicPropertyString : PropertyXhtmlString 
    {
        public override IPropertyControl CreatePropertyControl()
        {
            return new PropertyDynamicPropertyStringControl();
        }
    }

    [PropertySettings(typeof(TinyMCESettings), true)]
    public class PropertyDynamicPropertyStringControl : PropertyXhtmlStringControl
    {
        public override void ApplyEditChanges()
        {
            // Get Text
            string textToFix = this.EditControl.Text;

            // Get root node
            HtmlNode rootNode = MakeHtmlNode(textToFix);

            // Get span nodes
            string dyanicProperyHtml = "";
            Dictionary result = new Dictionary();
            HtmlNodeCollection collection1 = rootNode.SelectNodes("//span[@dynamicclass]");
            if (collection1 != null)
            {
                foreach (HtmlNode dyn in collection1)
                {
                    dyanicProperyHtml += dyn.OuterHtml;
                }
            }

            // If we have dynamica properties, use only these. Otherwise use original text. 
            dyanicProperyHtml = String.IsNullOrEmpty(dyanicProperyHtml) ? textToFix : dyanicProperyHtml;

            // Set Edit Value
            base.SetValue(dyanicProperyHtml);
        }



        public HtmlNode MakeHtmlNode(string html)
        {
            try
            {
                HtmlDocument doc = new HtmlDocument();
                doc.LoadHtml(html);
                return doc.DocumentNode;
            }
            catch { }
            return null;
        }

    }

I did consider using Anders dynamic content preview functionality but decided not to as I would be then be heading down a pseudo EPiServer Composer road which could cause Editor Interface issues with multiple items in one RTE field.

Download Code including Dynamic Content Controls, Page Type, Custom Property

1 comment:

  1. Hi,

    You have one, two, three column splitters.

    How you get plugin type from episerver list property and you fill it

    ReplyDelete