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/DynamicPlaceholderKeys_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 Then it’s the simple task of replacing your sc:placeholders on containers you want to repeat. i.e.: 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:<add tagPrefix="tcuk" namespace="Tcuk.Cms" assembly="Tcuk.Cms" />
<%@ Control Language="c#" AutoEventWireup="true" %>
<div>
<tcuk:DynamicKeyPlaceholder ID="Placeholder1" runat="server" Key="halfleft" />
</div>
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