Enable dynamic MVC content place holders for Sitecore – Part 2

This is the second part of a blog series talking about enabling dynamic content place holders with meaningful names using MVC.

Let’s see how we can bring the page editor to life with what was described in the first part of this series. As a starting point I got inspired by an article on Stackoverflow.

Customised GetAllowedRenderings

This customisation is required so that the we only need one entry in the Placeholder Settings section. Out-of-the box Sitecore is loading these settings based on the place holder name but since in our case that name is dynamic you would have to add an entry for each dynamic place holder which would be very cumbersome.

namespace HelveticSolutions.Sitecore.Mvc
{
    using System.Collections.Generic;
    using System.Text.RegularExpressions;

    using global::Sitecore;
    using global::Sitecore.Data;
    using global::Sitecore.Data.Items;
    using global::Sitecore.Diagnostics;
    using global::Sitecore.Pipelines.GetPlaceholderRenderings;

    /// <summary>
    ///     Customized allowed rendering component, which enables dynamic placeholders in the page editor.
    /// </summary>
    public class GetDynamicKeyAllowedRenderings : GetAllowedRenderings
    {
        /// <summary>
        ///     Regex matching pattern when placeholder name ends with a GUID
        /// </summary>
        private const string DynamicKeyRegex = @"(.+)__[\d\w]{8}\-([\d\w]{4}\-){3}[\d\w]{12}";

        /// <summary>
        ///     Regex matching pattern when placeholder name ends with any parameter based postfix
        /// </summary>
        private const string RowParameterRegex = @"(.+)__(.+)";

        public new void Process(GetPlaceholderRenderingsArgs args)
        {
            Assert.IsNotNull(args, "args");

            string matchedPlaceholderName;
            bool fallbackMatched;
            if (!TryMatchRegex(args.PlaceholderKey, out matchedPlaceholderName, out fallbackMatched))
            {
                return;
            }

            // Same as Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings but with fake placeholderKey
            Item placeholderItem = null;
            if (ID.IsNullOrEmpty(args.DeviceId))
            {
                placeholderItem = Client.Page.GetPlaceholderItem(
                    matchedPlaceholderName,
                    args.ContentDatabase,
                    args.LayoutDefinition);
            }
            else
            {
                using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
                {
                    placeholderItem = Client.Page.GetPlaceholderItem(
                        matchedPlaceholderName,
                        args.ContentDatabase,
                        args.LayoutDefinition);
                }
            }

            List<Item> collection = null;
            if (placeholderItem != null)
            {
                bool flag;
                args.HasPlaceholderSettings = true;
                collection = this.GetRenderings(placeholderItem, out flag);
                if (flag)
                {
                    args.CustomData["allowedControlsSpecified"] = true;
                    args.Options.ShowTree = false;
                }
            }

            if (collection != null)
            {
                if (args.PlaceholderRenderings == null)
                {
                    args.PlaceholderRenderings = new List<Item>();
                }

                args.PlaceholderRenderings.AddRange(collection);
            }
        }

        /// <summary>
        /// Tries to match any of the constants based matching patterns.
        /// </summary>
        /// <param name="value">
        /// The value trying to be matched.
        /// </param>
        /// <param name="matchedValue">
        /// The matched value.
        /// </param>
        /// <param name="matchedFallback">
        /// A flag indicating whether the fallback (GUID) matched.
        /// </param>
        /// <returns>
        /// Status whether a match was successful.
        /// </returns>
        internal static bool TryMatchRegex(string value, out string matchedValue, out bool matchedFallback)
        {
            matchedFallback = false;
            Regex regex = new Regex(RowParameterRegex);
            Match match = regex.Match(value);
            if (match.Success && match.Groups.Count > 0)
            {
                matchedValue = match.Groups[1].Value;
                return true;
            }

            // Fallback with GUID match
            regex = new Regex(DynamicKeyRegex);
            match = regex.Match(value);
            if (match.Success && match.Groups.Count > 0)
            {
                matchedValue = match.Groups[1].Value;
                matchedFallback = true;
                return true;
            }

            matchedValue = string.Empty;
            return false;
        }
    }
}

PlaceholderSettingsForRow

Customised GetDynamicPlaceholderChromeData

This customisation is required so that the place holder name can be overridden by the place holders display name in case the fallback (using the renderings unique identifier as suffix) is used.

namespace HelveticSolutions.Sitecore.Mvc
{
    using System;

    using global::Sitecore;
    using global::Sitecore.Data.Items;
    using global::Sitecore.Diagnostics;
    using global::Sitecore.Pipelines.GetChromeData;
    using global::Sitecore.Web.UI.PageModes;

    /// <summary>
    /// Customized chrome data processor, which hides the postfixes of dynamic placeholders.
    /// </summary>
    public class GetDynamicPlaceholderChromeData : GetChromeDataProcessor
    {
        #region Public Methods and Operators

        /// <summary>
        /// The process.
        /// </summary>
        /// <param name="args">
        /// The args.
        /// </param>
        public override void Process(GetChromeDataArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            Assert.IsNotNull(args.ChromeData, "Chrome Data");

            if ("placeholder".Equals(args.ChromeType, StringComparison.OrdinalIgnoreCase))
            {
                string matchedPlaceholderName;
                bool fallbackMatched;
                if (
                    !GetDynamicKeyAllowedRenderings.TryMatchRegex(
                        args.CustomData["placeHolderKey"] as string,
                        out matchedPlaceholderName,
                        out fallbackMatched))
                {
                    return;
                }

                if (fallbackMatched)
                {
                    // Handles replacing the displayname of the placeholder area to the master reference
                    if (args.Item != null)
                    {
                        string layout = ChromeContext.GetLayout(args.Item);
                        Item item = Client.Page.GetPlaceholderItem(matchedPlaceholderName, args.Item.Database, layout);
                        if (item != null)
                        {
                            args.ChromeData.DisplayName = item.DisplayName;
                        }

                        if ((item != null) && !string.IsNullOrEmpty(item.Appearance.ShortDescription))
                        {
                            args.ChromeData.ExpandedDisplayName = item.Appearance.ShortDescription;
                        }
                    }
                }
            }
        }

        #endregion
    }
}

Pipeline processor configuration

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <getPlaceholderRenderings>
        <processor patch:before="*[@type='Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings, Sitecore.Kernel']"
                   type="HelveticSolutions.Sitecore.Mvc.GetDynamicKeyAllowedRenderings, HelveticSolutions.Sitecore"
                   />
      </getPlaceholderRenderings>

      <getChromeData>
        <processor patch:after="*[@type='Sitecore.Pipelines.GetChromeData.GetPlaceholderChromeData, Sitecore.Kernel']"
                   type="HelveticSolutions.Sitecore.Mvc.GetDynamicPlaceholderChromeData, HelveticSolutions.Sitecore"
                   />
      </getChromeData>
    </pipelines>
  </sitecore>
</configuration>

Enjoy Page Editor support for dynamic MVC content place holders

Here’s the sample page with the two column modules rendered in a row wrapper rendering:

PageEditorSupportForDynamicContentPlaceholders

Leave a Reply

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