Dynamic Placeholder Keys in Sitecore

Sitecore have spent a long time designing a CMS which provides great flexibility for both developers and content editors. If you have designed your renderings and placeholders in an intelligent way a seemly complex page can become a playground for content editors. Despite Sitecore’s huge improvements throughout the years one fundamental problem however still seems to remain – or should I say, I have not yet found a good out of the box solution! Perhaps Sitecore themselves can prove me wrong.

Consider a simple page with a container rendering (5050Container.ascx for example) which splits the page in two:

<%@ Control Language="c#" AutoEventWireup="true"  %>
 <div>
 <sc:Placeholder ID="Placeholder1" runat="server" Key="halfleft" />
 </div>
 <div>
 <sc:Placeholder ID="Placeholder2" runat="server" Key="halfright" />
 </div>

We could put control’s into the left and right placeholders via the Page Editor or indeed via Layout Details.

Control A’s placeholder path would represent /main/halfleft.
Control B’s placeholder path would represent /main/halfright.

Now consider we would like to add a second row using the same container – We hit a problem when we try and add controls into the placeholders of this second container because the placeholder paths resolve to the first container.

One solution is to create a second container (e.g. 5050Container2.ascx) with different keys:

<%@ Control Language="c#" AutoEventWireup="true"  %>
 <div>
 <sc:Placeholder ID="Placeholder1" runat="server" Key="halfleft2" />
 </div>
 <div>
 <sc:Placeholder ID="Placeholder2" runat="server" Key="halfright2" />
 </div>

This is quite untidy, and if content editors want to add many rows where do you stop, Sitecore could become full of multiple versions of the same container rendering.

A better approach would involve just one rendering which we would want repeated, again for example our 5050Container.ascx. We would want to automatically suffix placeholder keys each time a repeating container is added to the page. For example, if we had two 5050Containers’s on the page the keys for the first container would dynamically be generated as halfleft1, halfright1 and then for the second container halfleft2, halfright2 and so on.

Before I started implementing this approach I had a quick browse on Google and came across a very good article by Nick (@techphoria414) http://www.techphoria414.com/Blog/2011/August/Dynamic_Placeholder_Keys_Prototype. Nick had a similar idea which I’ve used to implement my approach.

The concept is quite simple, create our own implementation of the sc:Placeholder control and let this decide whether the container should be repeated, if so change the key.

Containers need to be repeated if:

–          They sit in the same placeholder.
–          They are the same control.

I have pasted the code at the bottom of this post, but please bear in mind it is a prototype. Add the class to your solution and add its namespace/assembly to the <controls section of your web.config:

<add tagPrefix="tcuk" namespace="Tcuk.Cms" assembly="Tcuk.Cms" />

Then it’s the simple task of replacing your sc:placeholders on containers you want to repeat. i.e.:

<%@ Control Language="c#" AutoEventWireup="true"  %>
 <div>
 <tcuk:DynamicKeyPlaceholder ID="Placeholder1" runat="server" Key="halfleft" />
 </div>

You will notice in the page editor if you add multiple repeating containers the keys will be dynamically updated:

And this is reflected in the Layout Details:

Code Below:

public class DynamicKeyPlaceholder : WebControl, IExpandable
 {
        protected string _key = Placeholder.DefaultPlaceholderKey;
        protected string _dynamicKey = null;
        protected Placeholder _placeholder;
        public string Key
        {
            get
            {
                return _key;
            }
            set
            {
                _key = value.ToLower();
            }
        }
        protected string DynamicKey
        {
            get
            {
                if (_dynamicKey != null)
                {
                    return _dynamicKey;
                }
                _dynamicKey = _key;
                // Find the last placeholder processed.

Stack<Placeholder> stack = Switcher<Placeholder,
PlaceholderSwitcher>.GetStack(false);
               Placeholder current = stack.Peek();
                // Group of containers which sit in the current placeholder (i.e. they have the same placeholder id).
                var renderings = Sitecore.Context.Page.Renderings.Where(rendering => (rendering.Placeholder == current.ContextKey || rendering.Placeholder == current.Key) && rendering.AddedToPage);
                // Current container
               var thisRendering = renderings.Last();
                // get all repeating containers of same control on the page
                renderings = renderings.Where(i => i.RenderingItem != null && i.RenderingItem.ID == thisRendering.RenderingItem.ID);
                // Count - 1 represents how many of the same container have already been added to the page
                if (renderings.Count() > 0)
                {                   
                    _dynamicKey = _key + renderings.Count();
                }
                return _dynamicKey;
            }
        }
        protected override void CreateChildControls()
        {
            _placeholder = new Placeholder();
            _placeholder.Key = this.DynamicKey;
            this.Controls.Add(_placeholder);
            _placeholder.Expand();
        }
        protected override void DoRender(HtmlTextWriter output)
        {
            base.RenderChildren(output);
        }
        #region IExpandable Members
        public void Expand()
        {
            this.EnsureChildControls();
        }
        #endregion

8 thoughts on “Dynamic Placeholder Keys in Sitecore

  1. Simon July 12, 2012 / 9:02 am

    Thank you! Exactly what I was looking for.

  2. jerrod6age.modwedding.com March 26, 2013 / 5:54 am

    Good info. Lucky me I recently found your blog by accident (stumbleupon).
    I’ve bookmarked it for later!

  3. Amelie April 20, 2013 / 1:37 pm

    Heya i’m for the primary time here. I found this board and I in finding It truly useful & it helped me out a lot. I hope to give one thing back and aid others like you aided me.

  4. M November 22, 2013 / 12:20 pm

    Thanks, this is exactly what I needed for a similar scenario, but the dynamic keys result in placeholder settings set on the std values not to be applied anymore… any solution for that?

    M

  5. daveleigh December 3, 2013 / 5:13 pm

    Hi “M” – Unfortunately the only way round this is to duplicate your current placeholder settings for ‘Row’. e.g Find ‘row’ (which will not work any more because sitecore expects row1 – rowx now), copy it and rename the new item ‘row1’ (also update the ‘placeholder key’ field row -> row1). Repeat this a number times – I’ve gone up to 10 before. It’s not ideal but placeholder settings are quite hidden away from content editors so not really a problem for us.

  6. Matthew January 15, 2014 / 11:45 am

    Thanks for this. was fairly painless to implement thanks to this guide. The only thing i needed to modify was the retrieval of contains which sit in the current placeholder since we had placeholders with uppercase letters.

    Thought I’d post it here in case someone comes across the same issue:

    var renderings = Sitecore.Context.Page.Renderings.Where(rendering => (rendering.Placeholder.ToLower() == current.ContextKey || rendering.Placeholder.ToLower() == current.Key) && rendering.AddedToPage);

    Just added the .ToLower() after Placeholder field.

  7. sunny January 15, 2014 / 9:39 pm

    HI!
    in this piece of code
    var renderings = Sitecore.Context.Page.Renderings.Where(rendering => (rendering.Placeholder == current.ContextKey || rendering.Placeholder == current.Key) && rendering.AddedToPage);

    I’m getting a null reference in renderings var.

    Do you know why?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s