Skip to main content

implementing tabs using dynamic placeholders

Effectively creating a good editor experience when implementing tabs is difficult; you don't want to duplicate existing renderings, you want to reuse them. You don't want to create a new templated data source to define some tabs and their titles; you don't want to manipulate existing markup to cater for the tab markup.

When planning how I was going to implement tabs, I struggled to work out how I would be able to re-use existing placeholders to allow multiple renderings to sit within the tabbed content, with each tab being able to have a number of renderings, but remaining exclusive from the next tab from a markup perspective. Most of the above would be easily achievable with regular html; but when you want to reuse renderings and consider the editor experience it becomes difficult. Here's where the DynamicPlaceholderDefinition comes in.

Here are the steps I have taken to implement tabs by reusing existing renderings and creating a great editor experience:

  • Create a new view rendering Tabbed Content
  • Define a parameter template Tabbed Content Parameters consisting of two properties:
    • Tab Count - (this can be achieved by using ph_column_count as per the docs, however, I have decided to use my own prop for readability and consistency with the other props).
    • Tab Titles - this is going to be a pipe separated list of strings that'll be used to set the title of each tab
  • Ensure the Tabbed Content rendering is using the newly defined parameter template by setting the Parameters Template property.
  • For usability, I also set the Open Properties after Add property to Yes on the rendering so users are prompted to choose their tab count and tab titles on insertion.

The code for the Tabbed Content View Rendering consists of something like the following:

// using statements redacted

@{
var glassHtml = Html.Glass();
var parameters = glassHtml.GetRenderingParameters<ITabbed_Content_Parameters>();
var count = parameters.Tab_Count;
var tabTitles = parameters.Tab_Titles.Split('|');
var isExperienceEditorEditing = Sitecore.Context.PageMode.IsExperienceEditorEditing;
}

<div class="outer-div-for-whole-tab-module">
<ul>
@foreach (var tabTitle in tabTitles)
{
<li>@tabTitle</li>
}
</ul>
@Html.Sitecore().DynamicPlaceholder(new DynamicPlaceholderDefinition("content-placeholder")
{
OutputModifier = (input, context) => outputModifier(input, context),
Count = count,
Seed = 100
})
</div>

@functions{
static HtmlString outputModifier(IHtmlString input, DynamicPlaceholderRenderContext context)
{
var displayClass = context.Index > 0 && !Sitecore.Context.PageMode.IsExperienceEditorEditing ? "display-none" : string.Empty;
return new HtmlString($"<section class='tab-{context.Index} {displayClass}'>" + input + "</section>");
}
}

The most interesting bit of this code for me is the DynamicPlaceholderDefinition - this allows you to reuse an existing placeholder where your content modules are already defined, however, specifying a count and an outputModifier you have the ability to render multiple placeholders and specify surrounding markup for each of those placeholders. This means that the outer module can now consist of multiple placeholders which can form the tabs, and each of those can consist of multiple renderings.

info

Further reading of Dynamic Placeholders here