retail-theatre_1424x397px

Complex layouts in Sitecore using standard values hierarchy

Posted 1 February 2012 1:00 PM by Przemysław Taront

This post concerns Sitecore 6.5.0 (rev. 111123).


Sitecore 6.4.0 introduced an awesome feature called "layout deltas". In a nutshell, changes made to the presentation of an item are now stored as "deltas". In the rendering pipeline, information stored in the standard values for the template is automatically merged with the "delta" for the item. What is the benefit of this? You can modify the presentation of standard values and the changes will be automatically reflected for all pages based on the template.

Unfortunately, this mechanism works only between an item and the template that it is directly associated with. However, you may want to create a hierarchy of templates, wherein generic templates hold common parts of the presentation and specific templates add more details to it. An example:
  • Base template defines common header and footer
  • Content template inherits from the base template adding navigation and sidebar
  • Hub template inherits from content adding carousel control
  • Article template inherits from content adding title, author and article content to the presentation.
When we start implementing this hierarchy we will run into an issue: As long as a child template does not change the layout, i.e. __renderings field in the standard values is empty, the presentation is inherited from the parent. Once you start adjusting the presentation in a child template, Sitecore will copy __renderings field value from the parent and apply your changes there. Since standard values are not regular items, they store full definition of the layout, not "deltas"! So if you decide at a later instance, to change the footer from our example, you will need to apply the same change to all four layouts.

 Alistair Deneys in his blog proposed the so called Composite Presentation Inheritance to solve this problem. His approach works fine, but it affects rendering performance and uses "fake" layouts, which might be confusing to some editors. Below, I present an approach that is completely transparent to the Sitecore CMS users.

Implementation

The idea is to write an event handler that is fired on item saving event:
public void OnItemSaving(object sender, EventArgs args)
{
  Item item = Event.ExtractParameter(args, 0) as Item;
  PropagateLayoutChanges(item);
}
If the item being saved is a standard values item and the layout field is being changed, we can use XmlDelta utility (used by Sitecore to create "deltas") to calculate the difference. The final step is to apply this difference to standard values for templates that inherit directly or indirectly from the current one:
private void PropagateLayoutChanges(Item item)
{
  if (StandardValuesManager.IsStandardValuesHolder(item))
  {
    Item oldItem = item.Database.GetItem(item.ID, item.Language, item.Version);
    string layout = item[FieldIDs.LayoutField];
    string oldLayout = oldItem[FieldIDs.LayoutField];
    if (layout != oldLayout)
    {
      string delta = XmlDeltas.GetDelta(layout, oldLayout);
      foreach (Template templ in TemplateManager.GetTemplate(item).GetDescendants())
      {
        ApplyDeltaToStandardValues(templ, delta,
                                   item.Language, item.Version, item.Database);
      }
    }
  }
}
While applying the "delta" we skip the items that inherit the presentation from their standard values:
private void ApplyDeltaToStandardValues(Template template, string delta,
             Language language, Version version, Database database)
{
  Item item = ItemManager.GetItem(template.StandardValueHolderId, language,
                                  version, database, SecurityCheck.Disable);
  Field field = item.Fields[FieldIDs.LayoutField];
  if (!field.ContainsStandardValue)
  {
    string newFieldValue = XmlDeltas.ApplyDelta(field.Value, delta);
    if (newFieldValue != field.Value)
    {
      item.Editing.BeginEdit();
      LayoutField.SetFieldValue(field, newFieldValue);
      item.Editing.EndEdit();
    }
  }
}
Note that snippets above skip null checks and error handling.

Configuration

Add the following section to Sitecore configuration files and you are ready to go:
<events>
  <event name="item:saving">
    <handler type="Cognifide.SitecoreExtension.LayoutInheritance.ItemEventHandler,
      Cognifide.SitecoreExtension.LayoutInheritance" method="OnItemSaving"/>
  </event>
</events>
Note: Every time you introduce a change to the presentation in a base template, the change will be propagated to all descending templates.

Download source

Download package

to use with Installation Wizard from Sitecore Desktop


If you enjoyed this post or have any questions or comments, feel free to write them below.

Archive