One of the issues I had with Sitecore dynamic placeholders implementations available on the community was the fact that or they would have keys automatically generated using unique identifiers (GUIDs) or they would use an arbitrary numerical sequence.

The issue with the first one is basically a non-user-friendly placeholder key showing up in your presentation details, making it harder to identify which one of the dynamic placeholders was actually being used by a component.

The issue with the second one is that it defines the placeholder keys during rendering time following a numeric sequence. Therefore you would have something like placeholder_key_1, placeholder_key_2, placeholder_key_3. The problem here is that if you add components to placeholder_key_3 and then after if you decide to delete placeholder_key_2, the next time the page is rendered you would end up with your key_3 becoming key_2 (as it is now the second one and not the third one anymore) causing any components added to key_3 to simply disappear. Your content would still be there but on components that are assigned to a placeholder key that is not valid anymore.

To solve that problem I decided to change the implementation to generate the key by appending a suffix defined on a rendering parameter from the component holding the placeholder.

Using an implementation based on a grid system (Twitter Bootstrap or Zurb Foundation) we can have rows and cols as the base components holding our placeholders. This way a content author can easily use Experience Editor to create a structure like this:

Row_ph_Top

Col_ph_TopLeft Col_ph_TopMiddle Col_ph_TopLeft
Row_ph_Content

Col_ph_ContentLeft Col_ph_ContentRight
Row_ph_Content2

Col_ph_ContentLeft2 Col_ph_ContentRight2

In a grid system with relative column size of 12 it would be equivalent to:

4 4 4
4 8
8 4

In order to have the dynamic placeholder keys generated as defined above we will combine the holding component name (Row or Col) with a dynamic placeholder pattern defined in a config file (_ph_) and finally with a suffix defined in the col or row component being dynamically added to our page.

The code I am using to achieve that is based on dynamic placeholder implementations available on the web and my implementation of it using Foundation. I have blogged about it before on this post: Implementing Zurb Foundation grid in Sitecore 8.1 with MVC

The code files below are an updated version from that previous post to achieve what we want now (be able to define the placeholder key when adding a component that contains a dynamic placeholder).

Our DynamicPlaceholder.config file will look like this:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
 <sitecore>
 <pipelines>
<getPlaceholderRenderings>
 <processor
 type="Sitecore81Foundation.SitecoreExtensions.Pipelines.DynamicKeyPlaceholder.GetDynamicKeyAllowedRenderings, Sitecore81Foundation"
 patch:before="processor[@type='Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings, Sitecore.Kernel']"/>
 </getPlaceholderRenderings>
<getChromeData>
 <processor
 type="Sitecore81Foundation.SitecoreExtensions.Pipelines.GetChromeData.GetDynamicKeyPlaceholderChromeData, Sitecore81Foundation"
 patch:after="processor[@type='Sitecore.Pipelines.GetChromeData.GetPlaceholderChromeData, Sitecore.Kernel']"/>
 </getChromeData>
 </pipelines>
<!-- Pattern to use for dynamic placeholders -->
 <settings>
 <setting name="DynamicPlaceholderPattern" value="_ph_" />
 </settings>
</sitecore>
</configuration>

Here we have our updated Row.cshtml:

@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation
@using Sitecore.Configuration

@model RenderingModel
@{
 string dph = Settings.GetSetting("DynamicPlaceholderPattern");
 string suffix = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Parameters["Placeholder Suffix"];
 string placeholderKey = "row";
if (!String.IsNullOrEmpty(suffix)) { placeholderKey = placeholderKey + dph + suffix; }
}

<div class="row">
 @if (Sitecore.Context.PageMode.IsPageEditorEditing)
 {
 <br /><label>[@Html.Raw(placeholderKey)]</label>
 }

@Html.Sitecore().Placeholder(placeholderKey)
 @if (Sitecore.Context.PageMode.IsPageEditorEditing)
 { <br /><br /> }
</div>

You will notice that we are just using a regular out-of-the-box Html.Sitecore().Placeholder() method. The key is dynamically defined on rendering time not using a GUID defined on an extension class, but rather just the component name, the dynamic placeholder pattern (that makes our life easier to identify which placeholders are dynamic) and the suffix entered by the content author in a rendering parameters field.

And our updated Col.cshtml:

@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation
@using Sitecore.Configuration;
@model RenderingModel
@{
 string small = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Parameters["Small Devices"];
 string medium = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Parameters["Medium Devices"];
 string large = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Parameters["Large Devices"];

string dph = Settings.GetSetting("DynamicPlaceholderPattern");
 string suffix = Sitecore.Mvc.Presentation.RenderingContext.Current.Rendering.Parameters["Placeholder suffix"];
 string placeholderKey = "col";

if (!String.IsNullOrEmpty(suffix)) { placeholderKey = placeholderKey + dph + suffix; }
}

<div id="sfColumns" class="@Html.Raw(small) @Html.Raw(medium) @Html.Raw(large) columns">
 @if (Sitecore.Context.PageMode.IsPageEditorEditing) 
 {
 <br /><label>[@Html.Raw(placeholderKey)]</label> @* Required to make the column visible even when empty *@
 }

 @Html.Sitecore().Placeholder(placeholderKey)
 @if (Sitecore.Context.PageMode.IsPageEditorEditing)
 { <br /> }
</div>

Now we need to update our GetDynamicKeyAllowedRendering.cs file:

using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.GetPlaceholderRenderings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;

namespace Sitecore81Foundation.SitecoreExtensions.Pipelines.DynamicKeyPlaceholder
{
 public class GetDynamicKeyAllowedRenderings : GetAllowedRenderings
 {
  public new void Process(GetPlaceholderRenderingsArgs args)
  {
   string dphPattern = Settings.GetSetting("DynamicPlaceholderPattern");
   string placeholderKey = args.PlaceholderKey;

   if (placeholderKey.Contains(dphPattern))
   {
     placeholderKey = placeholderKey.Substring(0, placeholderKey.LastIndexOf(dphPattern));
     if (placeholderKey.LastIndexOf("/") >= 0)
     {
       placeholderKey = placeholderKey.Substring(placeholderKey.LastIndexOf("/") + 1);
     }
    }
    else
    {
      return;
    }

    Assert.IsNotNull((object)args, "args");
    Item placeholderItem = (Item)null;
    if (ID.IsNullOrEmpty(args.DeviceId))
    {
     placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase, args.LayoutDefinition);
    }
    else
    {
     using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
 placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase, args.LayoutDefinition);
    }
    List<Item> list = (List<Item>)null;
    if (placeholderItem != null)
    {
     args.HasPlaceholderSettings = true;
     bool allowedControlsSpecified;
     list = this.GetRenderings(placeholderItem, out allowedControlsSpecified);
     if (allowedControlsSpecified)
     {
      args.CustomData["allowedControlsSpecified"] = true;
      args.Options.ShowTree = false;
     }
    }

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

And finally we update the GetDynamicPlaceholderChromeData.cs file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sitecore.Pipelines.GetChromeData;
using System.Text.RegularExpressions;
using Sitecore.Diagnostics;
using Sitecore81Foundation.SitecoreExtensions.Pipelines.DynamicKeyPlaceholder;
using Sitecore.Data.Items;
using Sitecore.Web.UI.PageModes;
using Sitecore.Configuration;

namespace Sitecore81Foundation.SitecoreExtensions.Pipelines.GetChromeData
{
 public class GetDynamicKeyPlaceholderChromeData : GetPlaceholderChromeData
 {
  public override void Process(GetChromeDataArgs args)
  {
   string dphPattern = Settings.GetSetting("DynamicPlaceholderPattern");

   Assert.ArgumentNotNull(args, "args");
   Assert.IsNotNull(args.ChromeData, "Chrome Data");
   if ("placeholder".Equals(args.ChromeType, StringComparison.OrdinalIgnoreCase))
   {
     string placeholderKey = args.CustomData["placeHolderKey"] as string;

     // Is a Dynamic Placeholder
     if (placeholderKey.Contains(dphPattern))
     {
       placeholderKey = placeholderKey.Substring(0, placeholderKey.LastIndexOf(dphPattern));
       if (placeholderKey.LastIndexOf("/") >= 0)
       {
         placeholderKey = placeholderKey.Substring(placeholderKey.LastIndexOf("/") + 1);
       }
    }

    args.ChromeData.DisplayName = placeholderKey;
    args.ChromeData.ExpandedDisplayName = placeholderKey + " Dynamic Placeholder";
   }
  } 
 }
}

Row and Col will have the Placeholder suffix field added to their rendering parameters template.

At /sitecore/templates/SFoundation/Rendering Parameters/Row I have:

renderingparameters

Note: in our original implementation of the Grid with Foundation, Col also has three other fields to define its relative size on small, medium and large devices. These fields should be preserved. Row didn’t have placeholder parameters, so it will now have one using only the Placeholder suffix field.

Update: please, in the screenshots read it Suffix, not Sufix (my typo!)

And we are now able to add Rows and Cols in Experience Editor and define the suffix to be used for each one of them:

topleft

This approach preserves your definition for Allowed Controls made to the placeholder keys row and col. The GetDynamicKeyAllowedRenderings class will remove the pattern and suffix and use only the portion before the dynamic key pattern as the key to look for any allowed renderings.

Final result:

final

What happens if they leave the suffix field empty? It will just use the default key: row or col, working as it would normally work as a non-dynamic placeholder. Therefore, leaving more than two blank will just have the old issue of two placeholders with the same key. The same will also happen if the author uses the same suffix for two different rows or two different columns. Therefore, there is still room for improvement to validate this scenarios or to automatically generate a random key and update the parameter field when it is left blank.

Another improvement for the future is when an author renames the suffix. Currently, if the suffix for a placeholder is renamed the components it may currently be holding will not be automatically updated to the new name. This is my next step and I will share it with your here once I have it implemented.

And that’s it. Hope you have enjoyed.