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
- Tab Count - (this can be achieved by using
- Ensure the
Tabbed Content
rendering is using the newly defined parameter template by setting theParameters Template
property. - For usability, I also set the
Open Properties after Add
property toYes
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.
Further reading of Dynamic Placeholders here