When implementing Sitecore websites, we sometimes run into a situation where the Content Editor wants to personalize or A/B test components that are common to multiple pages in the site. A good example of this is the header and/or navigation components.
The problem we run into is that the components need to behave the same on all pages and it would be very difficult, if not impossible for content editors to maintain these components on all pages on the site. I have seen a few ways of solving this problem, but most solutions had some drawbacks that limited the capabilities of the content editor.
Implementing common renderings
To have common renderings across multiple pages we need to make sure the renderings on each page are the same. This is easily done in Sitecore, but if the content editor wants to personalize or A/B test these items, things get difficult.
Adding Rules to Data Items
One way of solving is to add a rules field to the data items the DataSource of a component points at. The rules would be evaluated at render time and a decision would be made to determine if the item should be shown.
This has at least two big drawbacks. The first is that the content editors can't make changes to these items the same way they do for other renderings on the site. Since everything is edited in the rules for the individual items, content editors need to have special procedures for these common components. The second is that it makes A/B testing impossible using the default Sitecore components.
Editing the __Standard values
Using __Standard values to hold common components is a reasonable way to build default functionality into the pages of your site. The presentation details on the __Standard values item is shared across any page item that use the template. This makes it very tempting to use to __Standard values to manage shared page components.
The big drawback with this technique is that content editors need to edit the __Standard values if they want to change the behavior of the site. I feel that __Standard values are developer items, and any process that involves content editors manipulating developer items should be avoided at all costs.
A better choice
Fallback is when a value is left blank or empty and the value is retrieved from some other location in a predictable way. Content Editors already understand field fallback, item fallback and language fallback. Why not have fallback functionality on placeholders.
Placeholder fallback would use the Sitecore Item hierarchy to implement parent fallback for the contents of a placeholder. The content editor can edit the header and navigation on a top level page in the Experience Editor and it will automatically appear on all child pages. If they want to provide page specific renderings, they can easily do so by adding the proper renderings to any sub-page in the hierarchy.
Placeholder fallback will allow personalization and A/B testing automatically work using the out of the box components. As an added benefit, the content editor can customize the renderings exactly like all other items on the site. The customizations will automatically be used on the child pages of the site because the child pages fall back to whatever the parent rendering does.
Implementation
There are only two main pieces needed to implement fallback renderings. The first is a simple rendering that is placed on the page to indicate where parent renderings should be inserted. This component is added by the __Standard values for all pages and can be removed by the content editor of they want to override the renderings on a specific page. The second component is a pipeline step that runs as part of the
mvc.getXmlBasedLayoutDefinition pipeline and calculates the replacement values for the fallback renderings.
Placeholder fallback rendering
When in the experience editor, the rendering presents itself as a square box with a message telling the Content Editor where the actual renderings for the page will come from. We use a controller rendering to implement this. The controller action is very simple and can be located on whatever controller the developer wants:
/// <summary>
/// If a fallback rendering is shown on the page, make it invisible.
/// </summary>
/// <returns></returns>
public ActionResult FallbackRendering()
{
if (Sitecore.Context.PageMode.IsPageEditor || Sitecore.Context.PageMode.IsPreview)
{
return View(new FallbackRenderingInfo
{
FallbackLocation = RenderingContext.Current.Rendering.
Properties[InsertFallbackRenderings.FALLBACK_LOCATIONS_PROPERTY]
});
}
else
{
return new EmptyResult();
}
}
The view for the rendering is also very simple and looks like:
@inherits System.Web.Mvc.WebViewPage<TDS.SitecoreCustomizations.Models.FallbackRenderingInfo>
<div class="fallback-rendering">
<div>Fallback rendering</div>
<p>This content may not be edited here.</p>
<p>Content for this placeholder comes from the page(s) '@Model.FallbackLocation'</p>
</div>
The developer may want to implement some css to make the rendering look good on the page. In our case it is a gray box with a black border and the text centered in the box.
The rendering also has a rendering parameter template associated with it that allows it to be configured with a comma separated list of placeholder names. These are the placeholders the pipeline step uses to get the parents renderings for the page.
Getting the layout from the parent
The pipeline step to implement Placeholder Fallback is inserted after Sitecore.Mvc.Pipelines.Response.GetXmlBasedLayoutDefinition.GetFromLayoutField, Sitecore.Mvc step in the mvc.getXmlBasedLayoutDefinition pipeline. This step parses the current layout XML, looks for the fallback rendering and replaces it with renderings from an ancestor item.
The code for insert fallback renderings is a little longer than I want to include in the blog, so you can download the .cs file here: InsertFallbackRenderings.cs. I will describe the operation of the class below.
Constants
There are two constants in the class. The first is the only one you need to worry about. It is the ID of the fallback rendering item in Sitecore. It is called
FALLBACK_ RENDERING_ID. You should change this constant to match the ID of the fallback rendering item you create.
ProcessFallbackRendering method
The ProcessFallbackRendering method does most of the hard work. It looks for the fallback renderings in the current layout Xml. Then it parses the parameters to find the placeholder names.
If the page is in page editor mode, it locates the replacement renderings, but puts the placeholder names and parent path in the fallback rendering XML instead of doing the actual fallback. The placeholder and parent paths are picked up by the fallback rendering and shown to the content editor so they know where the actual renderings will come from.
If the page is not is page editor mode, the replacement renderings are inserted in the current layout Xml and the placeholder rendering is removed.
FindPlaceholderReplacement
Gets the layout Xml for each parent until the list of required placeholders has been retrieved. This method uses calls to a delegate Func<> to determine if the layout for the parent item is actually useful. This allows the traverse functionality to be reused multiple ways.
GetFallbackRenderings
Determines which renderings in the passed layout XML are renderings that can be added to the placeholder. If is sees a fallback rendering in the placeholder, it is bypassed, allowing parent renderings to be searched.
GetLayoutXml
Returns the layout Xml for a page using the same API as Sitecore does to determine the layout for a page. This ensures that the rules for the shared/final renderings are evaluated correctly.
Final Thoughts
This solution was well received by the content editors who have used it. They preferred it over other implementations because it provided an editing experience very much like all the other editing experiences on the site. They also found it very easy to understand.
The implementation was done on Sitecore 8 Update 3, 4 & 5. It is only compatible with MVC renderings, but could most likely be ported to ASP.Net Sublayouts.