Dynamic Placeholders and IExpandable

There have been quite a few posts on the topic of ‘Dynamic Placeholders’ (among the most useful of which are those of John Newcombe, Nick Wesselman and Dave Leigh).  If you haven’t come across this topic, then I’d suggest checking out Nick Wesselman’s blog post for a pretty succinct explanation of it.  It boils down to the limitation that you cannot place multiple sublayouts containing a placeholder onto a page in such a way that the placeholders resolve to having the same fully qualified placeholder keys.  I really like John Newcombe’s approach, which is based on Nick Wesselman’s; and having spent quite a bit of time playing with it, I’d certainly suggest that anyone needing to solve this problem takes a look themselves.

I plan to tackle a slightly different problem in this post: that of dynamically creating a number of placeholders within a single sublayout (so dynamic placeholders instead of dynamic placeholder keys, although as we’ll see later, the two problems can be solved in a very similar manner).  Let’s take the following scenario:

  1. You have a sublayout for a tab panel
  2. The tab panel can have a variable number of tabs on it
  3. Each tab has its own placeholder control, which allows for further sublayouts/renderings to be inserted inside it.
  4. To make things a bit easier, let’s also say that there can be no more than 5 tabs on this control.

So you go off and create a sublayout that either uses CreateChildControls, or a Repeater, or some other such mechanism to create the various mark-up and placeholder controls that you need for each tab.  Perhaps you think ahead and add the tab index to the key of each placeholder (e.g. “tab-1”, “tab-2”, and so on).  You fire up the Sitecore page editor and add your control, and are delighted to see the empty placeholder hatching on each of the tabs.  Then you add a control to one of the placeholders and save the item.  It doesn’t appear.  You check page editor; it’s definitely in the presentation settings with the correct key.  You check debug mode, and you see that Sitecore is expanding “tab-1” but not “/content/tab-1”, which is what the absolute placeholder path should be.  What gives?!  You have a bit of a Google around, and find a few posts that suggest changing the LayoutPageEvent setting within Sitecore.  You make the change, but still no dice.  Hmm…

I have to admit that I haven’t fully unraveled everything that goes on myself (so please correct me if I’m mistaken), however I believe that Sitecore scans the Controls collection of any renderings that it creates for Placeholder controls when it actually creates them (during, I believe, the insertRendering pipeline).  Any that it locates, it adds to a collection for processing later.  For some reason, this seems to affect how it treats the placeholder key when it actually comes to resolving the renderings that are being inserted into it.  Only the placeholders that are picked up in the initial pass will be processed using the absolute placeholder keys (e.g. “/content/tab-1”) – any others will only ever be processed with their “relative” key (“tab-1”).

Fortunately, resolving this is pretty simple.  Sort of.  Before Sitecore inspects the Controls collection for Placeholders, first it checks if the sublayout implements IExpandable; and if it does, then it calls IExpandable‘s Expand method.  So, if you implement IExpandable on your sublayout, and then make it so that the Expand method calls whatever logic you have that creates the placeholders then you should be good to go.  Unfortunately, this is where the “sort of” kicks in.  It turns out that this Expand method is called before the control is added to the page.  As such, if your logic is dependent upon the DataSource of the control, and if whatever you use to get the DataSource works by finding the parent Sublayout control, you’ll get null back and most likely either end up with no panels or a NullReferenceException.

Nick Wesselman’s post contains a mechanism for getting the rendering that is currently being processed; and fortunately that can be trivially adapted to retrieve the DataSource (or also Parameters) for the currently processing RenderingReference.  You can either refer to the previously linked blog post or use the following code to get the RenderingReference:

public static RenderingReference GetCurrentRendering()
{
    // Get the current placeholder stack
    var stack = Switcher<Placeholder, PlaceholderSwitcher>.GetStack(false);
    if (stack == null || stack.Count == 0)
    {
        // Not used within a placeholder - maybe in a layout or something?
        return null;
    }

    // Get the current placeholder
    var current = stack.Peek();

    //find the rendering reference we are contained in
    return Sitecore.Context.Page.Renderings
                    .Where(rendering => rendering.Placeholder == current.ContextKey || rendering.Placeholder == current.Key)
                    .LastOrDefault(rendering => rendering.AddedToPage);
}

Once you put that in place, the last thing that you need to do is call Expand() on your dynamic placeholders from your sublayout’s Expand method.  After you’ve done that, you should then see your placeholders start to behave.  One thing that I would suggest, though, is using something other than a number to distinguish your placeholders; for example, if each tab is represented by an item, then I will consider using that item’s ID in the placeholder name.  It’s not terribly friendly, however in page editor, you rarely have to care about specific placeholder names (aside from placeholder settings, which the aforementioned blog posts already address).

So they key to the functionality is the IExpandable interface.  Something that not very many people know about, and so something that I thought was worth sharing.  Have a play with it; and hopefully this post will help someone out.

One thought on “Dynamic Placeholders and IExpandable”

Comments are closed.