Sitecore 9: Dynamic Placeholders

I’d be very surprised if you were not using some sort of implementation of dynamic placeholders in your projects already. Most of us have probably been using a variant of Dynamic Placeholders that Nick Wesselman wrote way back in 2011, my personal favourite being Fortis Dynamic Placeholders by Jason Bert. This is also the most requested feature on the Sitecore Developer Uservoice and Sitecore Community Uservoice.

The wait if finally over. Sitecore 9 introduces support for dynamics placeholder implementation within the core product.

Why Dynamic Placeholders

Very simply, placeholders within a page must be unique. You can only have a single placeholder within a page with a specific name.

Let’s say you have a two column layout with a “left column” and a “right column” placeholder, this cam only appear once on a page, you cannot add the placeholder component multiple times. If you do then any components you add will be duplicated in all placeholders with the same name.

This made the reuse of layout components inflexible. One workaround was to duplicate the components (“left-column2”, “right-column2”) but that mean duplicating it several times and dealing with duplicating Insert Options. The better option was to use a Dynamic Placeholder implementation as mentioned earlier.

Placeholders. Placeholders Everywhere!

Pretty much every single project ever uses an implementation of Dynamic Placeholders so it makes sense to provide this in the platform. The in-built version takes a similar approach to some of the community contributed efforts, with some additional twists to add some extra flexibility.

The implementation can be found in the Sitecore.Mvc namespace, a.k.a. this is an MVC-only implementation. Sorry Webforms folks.

<div class="row">
    <div class="large-6 small-12 columns">
        @Html.Sitecore().DynamicPlaceholder("leftcolumn")
    </div>
    <div class="large-6 small-12 columns">
        @Html.Sitecore().DynamicPlaceholder("rightcolumn")
    </div>
</div>

The above makes sense if you have different placeholders with different Allowed Controls settings, but not so much when all placeholders should be the same. Each placeholder within a rendering has no knowledge other placeholders so you must use a seed number so the suffix will not clash. By default the seed is 0 based.

<div class="row">
    <div class="large-4 small-12 columns">
        @Html.Sitecore().DynamicPlaceholder("placeholderkey")
    </div>
    <div class="large-4 small-12 columns">
        @Html.Sitecore().DynamicPlaceholder("placeholderkey", seed: 100)
    </div>
    <div class="large-4 small-12 columns">
        @Html.Sitecore().DynamicPlaceholder("placeholderkey", seed: 200)
    </div>
</div>

You can see that there is some repetition above in the placeholder column definition, one of the aces up the sleeves of this implementation is the ability to specify the number of placeholder Sitecore should render, and the HTML that each should be wrapped in:

<div class="row">
    @Html.Sitecore().DynamicPlaceholder("placeholderkey", CreateColumnWrapper(), count: 3)
</div>

@functions
{
    TagBuilder CreateColumnWrapper()
    {
        var tagBuilder = new TagBuilder("div");
        tagBuilder.AddCssClass("large-4 small-12 columns");
        return tagBuilder;
    }
}

This results in 3 placeholder like the previous example, by the suffix is auto-generated and sequential as you would expect (i.e. _1, _2, _3) and no need to use a seed.

It’s also possible to generate an specific markup for each individual placeholder based on the DynamicPlaceholderRenderContext which can be passed in. Let’s say as an example that you wanted to add an additional CSS class on every 3 placeholder, which would allow you to have a maximum of 3 placeholders per “row” you could do something like the following:

<div class="row">
    @Html.Sitecore().DynamicPlaceholder("placeholderkey", CreateColumnWrapper(), count: 6)
</div>

@functions
{
    TagBuilder CreateColumnWrapper(DynamicPlaceholderRenderContext ctx)
    {
        var tagBuilder = new TagBuilder("div");

        string endClass = (ctx.Index + 1) % 3 == 0 ? " end" : "";

        tagBuilder.AddCssClass("large-4 small-12 columns" + endClass);
        return tagBuilder;
    }
}

If you follow the DynamicPlaceholder helper code through then you’ll notice that all methods eventually end up declaring a DynamicPlaceholderDefinition and setting the Output modifier property. You can call it directly:

<div class="row">
    @Html.Sitecore().DynamicPlaceholder(new DynamicPlaceholderDefinition("placeholderkey")
    {
        Count = 3,
        MaxCount = 9,
        Seed = 9,
        OutputModifier = (input, context) => new HtmlString("<div class=\"large-4 small-12 columns\">" + input + "</div>"),
    })
</div>

Customizing Placeholders using Rendering Parameters

You can then specify the number of placeholder spots to render from the Experience Editor using Rendering Parameters:

It is also possible to specify a maxCount parameter on your dynamic placeholder definition to restrict the Content Editors from going too crazy:

DynamicPlaceholder(string placeholderName, Func<DynamicPlaceholderRenderContext, TagBuilder> chromeResolver, int count = 1, int maxCount = 0, int seed = 0)

Used with a TagBuilder function this will allow your content editors to dynamically set the number of placeholders/columns to create. If you wanted a dynamic column definition allowing your users to set between 1 and 4 columns you could do something like this:

<div class="row">
    @Html.Sitecore().DynamicPlaceholder("customkey", CreateDynamicColumn(), count: 1, maxCount: 4)
</div>

@functions
{
    TagBuilder CreateDynamicColumn(DynamicPlaceholderRenderContext ctx)
    {
        var tagBuilder = new TagBuilder("div");
        int[] widths = new int[] { 0, 12, 6, 4, 3 };

        tagBuilder.AddCssClass("large-" + widths[ctx.PlaceholdersCount] + " columns");
        return tagBuilder;
    }
}

Bear in mind that you probably want to create a custom Rendering Parameters template and provide a drop list selection for your users to make this more user friendly. And also the above is sample code, just like Habitat 😝

Custom Placeholder Parameters

Just like the placeholder count, you can pass through additional parameters to the DynamicPlaceholderRenderContext and access them from the Parameters property. The parameters must follow a specific format:

  • ph_placeholderKey_paramName
    where placeholderKey is the key in your code (customkey in last example) and paramName is the parameter name that will be passed through. Count is a special param name that defines the number of placeholders to generate, as we saw earlier.

  • ph_placeholderKey_indexNumber_paramName
    where indexNumber is the placeholder index number. This allows you to only pass specific parameters to specific placeholders, e.g. If you wanted to pass a different CSS Class to the second placeholder to make it “featured”. Keep in mind that the number is 0 based.

With these settings, cssclass parameter is passed to all placeholders and extra is only passed to the 2nd placeholder (since it is zero based). We can then access these parameters and do something with them:

@functions
{
    TagBuilder CreateColumnWrapper(DynamicPlaceholderRenderContext ctx)
    {
        var tagBuilder = new TagBuilder("div");
        tagBuilder.AddCssClass("large-4 small-12 columns");

        string cssClass = ctx.Parameters["cssclass"];
        string extraClass = ctx.Parameters["extra"];

        if (!string.IsNullOrEmpty(cssClass))
            tagBuilder.AddCssClass(cssClass);

        if (!string.IsNullOrEmpty(extraClass))
            tagBuilder.AddCssClass(extraClass);

        return tagBuilder;
    }
}

Placeholder Key Generation

This probably looks very similar to other implementations such as Fortis, but the placeholder key generated is similar but different so it’s not a straight swap out.

By default the key will be generated in the format: {placeholder key}-{rendering unique suffix}-{unique suffix within rendering}

  • {placeholder key}
    The static placeholder key is passed in.

  • {rendering unique suffix}
    The Unique Rendering ID of the component that contains the placeholder. This is auto-generated in Sitecore every time you insert a component or add it to the Presentation Details. Since the ID is unique is guarantees uniqueness of the key across multiple renderings on a page.

  • {unique suffix within rendering}
    An index suffix to guarantee uniqueness within a rendering. Each occurrence of a dynamic placeholder is incremented by 1, starting at the seed value (default is 0).

For example, given a placeholder key of "leftcontent" with a unique rendering ID of
{whyc4n7y-0ub3-myfr-13nd-7r011d4nc1n9} and count of 3 and seed of 10 the following unique placeholder keys would be generated:

leftcontent-{whyc4n7y-0ub3-myfr-13nd-7r011d4nc1n9}-10
leftcontent-{whyc4n7y-0ub3-myfr-13nd-7r011d4nc1n9}-11
leftcontent-{whyc4n7y-0ub3-myfr-13nd-7r011d4nc1n9}-12

As you can see, very similar to the Fortis but just a little different, which uses key_guid_x without any {}, the spacers are underscores and the first placeholder within a rendering has not seed suffix.

So same same, but different.

Richard Seal has written a post on upgrading the dynamic placeholders from Fortis to this new implementation and you can find a Sitecore PowerShell Extensions to make your life easy.

Key Generation using Pipelines

As with all things Sitecore, there is a pipeline for the key generation allowing you to customize this further if you require:

<mvc.getDynamicPlaceholderKeys patch:source="Sitecore.Mvc.config">
    <processor type="Sitecore.Mvc.Pipelines.Response.GetDynamicPlaceholderKeys.GetRenderingUniqueSuffix, Sitecore.Mvc"/>
    <processor type="Sitecore.Mvc.Pipelines.Response.GetDynamicPlaceholderKeys.GetUniqueKeysWithinRendering, Sitecore.Mvc"/>
    <processor type="Sitecore.Mvc.Pipelines.Response.GetDynamicPlaceholderKeys.AggregatePlaceholderKeys, Sitecore.Mvc"/>
</mvc.getDynamicPlaceholderKeys>
<mvc.getDynamicPlaceholderInitialKey patch:source="Sitecore.Mvc.config">
    <processor type="Sitecore.Mvc.Pipelines.Response.GetDynamicPlaceholderInitialKey.RemovePlaceholderUniqueKeySuffix, Sitecore.Mvc" mode="on"/>
</mvc.getDynamicPlaceholderInitialKey>
  • GetRenderingUniqueSuffix
    This processor ensures that key placeholder key is unique on each page. By default it does this by retrieving the UID of the current rendering.

  • GetUniqueKeysWithinRendering
    This processor ensures uniqueness of multiple placeholders within a rendering by appending an incrementing numerical value to each generated key. The starting value is the seed value if it has been set, else the default is zero.

  • AggregatePlaceholderKeys
    As the name would suggest, this processor aggregates all parts of the unique placeholder key from the placeholder name, unique suffix id and unique id within the same placeholder as shown earlier.

You can override any of the default processors with your own custom logic, or supplement the logic, but be aware that you must also update the mvc.getDynamicPlaceholderInitialKey processor since this is responsible for trying to unfurl the unique placeholder key back into just the initial key, in order to then be able to set the correct Allowed Controls when you try to add new components.

Helper Methods

Here is an overview of the different signatures of the dynamic placeholders helper which can be found in the SitecoreHelper class in the Sitecore.Mvc.Helpers namespace.

DynamicPlaceholder(string placeholderName, int count = 1, int maxCount = 0, int seed = 0)

DynamicPlaceholder(string placeholderName, TagBuilder chrome, int count = 1, int maxCount = 0, int seed = 0)

DynamicPlaceholder (string placeholderName, Func<DynamicPlaceholderRenderContext, TagBuilder> chromeResolver, int count = 1, int maxCount = 0, int seed = 0)

DynamicPlaceholder(string placeholderName, Func<HtmlString, HtmlString> outputModifier, int count = 1, int maxCount = 0, int seed = 0)

DynamicPlaceholder(string placeholderName, Func<HtmlString, DynamicPlaceholderRenderContext, HtmlString> outputModifier, int count = 1, int maxCount = 0, int seed = 0)

DynamicPlaceholder(DynamicPlaceholderDefinition definition)

The parameters that can be passed in are as follows:

placeholderName name of the placeholder, must be non-empty string
count number of placeholders to render, must be non-negative interger
maxCount maximum number of placeholders that can be generated. Useful to restrict editors from going too crazy
seed starting value of placeholder key suffix used to ensure uniqueness within rendering
chrome a `TagBuilder` object that defines the markup that each instance of the placeholder should be wrapped in
Func chromeResolver a function that accepts a `DynamicPlaceholderRenderContext` object and return a `TagBuilder` that defines the markup the placeholder should be wrapped in, allowing each instance to return different markup.
Func outputModifier a function that accepts the HTML output of a placeholder and then return the modified HTML string
Func outputModifier like the previous overload, but also accepts an `DynamicPlaceholderRenderContext` object allowing yout o customize the return per placeholder
definition Allow you to specify all options defining a dynamic placeholder and pass a `DynamicPlaceholderDefinition` object

Bugs? Not the Bunny kind 😦

Or maybe it’s a feature? These are not bugs per se, just some gotchas that I noticed.

Normally I use dashes in my placeholder names, e.g. main-content. This works with dynamic placeholders as you would expect, but Additional Rendering Parameters does not let you specify dashes. It might be possibly using a Parameters Template to work round this, but be aware and test it does not cause issues.

Using underscores in placeholder names mean that custom parameters set through Rendering Parameters value were not being correctly passed to the DynamicPlaceholderRenderContext. This may be due to the way that regex works in Sitecore.Mvc.Presentation.DynamicPlaceholderParametersResolver. But then again, you know what they say about regex 😆

We also still have the age old problem of stray renderings remaining in the Presentation Details of a page if you remove a component with dynamic placeholders. If you then add the same rendering back into the page then the components will not magically pop back in to the placeholders rendering UID will be different. I liked the implementation in Fortis which would delete an “abandoned” components on item save.

Personally, I’m looking forward to using the new Dynamic Placeholders implementation in my projects. It’s super flexible and configurable. Hopefully there will be some SPE scripts released to help the community migrate from the other implementations.

Let me know if you have any questions.

Advertisements

4 comments

  1. Pingback: Sitecore 9 – New Core platform functionality | Kayee
  2. Pingback: Obligatory Sitecore Symposium Post – Nice Vieau
  3. trnktms · October 18

    found the easter egg 😀 whyc4n7y-0ub3-myfr-13nd-7r011d4nc1n9

  4. Pingback: Installing Sitecore 9 – How I did it | Visions In Code

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 )

Google+ photo

You are commenting using your Google+ 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 )

w

Connecting to %s