Share

LinkedIn

Sitecore Base Layouts Part I

Check out this new solution to allow Sitecore users to easily build hierarchies of shared presentation settings in page editor.

Sitecore presentation details can be stored on page items and on template standard values items. There has been a long-standing official recommended practice to set presentation details on the standard values items. However, when using the Page/Experience Editor (P/E Editor) interface, the settings are stored on the page item. These two recommended practices that are somewhat at odds.

The recommended practice to store presentation details on the template standard values whenever possible, to me, seems to be somewhat outdated. It’s fine to store global presentation settings, like the layout and placeholder settings on the standard values item. However, if you store more than that, you run the risk of falling back into the page-type template pattern of the early days of Sitecore.

This page-type template pattern generally leads to two issues. First, you may end up with large templates that are tightly coupled to their presentation components. Second, you may end up with templates with no fields whose only purpose is to define presentation settings. Either way, using template standard values to store shared presentation details takes power away from your editors. They will need an admin or developer if they want to modify the settings on the standard values items.

Using P/E Editor interface, on the other hand, allows you to empower your editors. Unfortunately, Sitecore does not provide a way to allow editors to manage shared presentation settings using P/E Editor. All presentation setting changes made in this mode are stored as a layout delta on the page item.

What we really need is a way to store shared presentation details in a location where they can be managed by editor users. Also, they should not be coupled to a particular page-level data template. Users should be able to choose which shared presentation settings are applied to a page and change their mind without the help of a developer or admin.

As usual, it is possible to extend Sitecore to meet these requirements. It is implemented by overriding Sitecore’s standard values provider. The idea is to intercept a request for the standard value of a layout (__renderings) field and instead of providing the value from the standard values item, return the value from an item chosen by the editor. The layout delta is then processed against that item to produce the complete set of presentation settings.

First, we need a field that will allow editors to choose which item will be used for the base layout value. The Base Layout Template defines the Base Layout field as a simple droplink field with its source set to a folder where base layout items are stored. If you have a multi-site solution, you may want this to be a query so that each site can have its own set of base layouts. In order to make the Base Layout field available on your page items, you will need to add it as a base template.



Once you have your templates set up, you can replace the default standard values provider with the BaseLayoutStandardValuesProvider. This provider inherits the default StandardValuesProvider, but it also acts as a decorator so if you can use it with something like the Partial Language Fallback or Field Fallback module. If a request for a standard value is for a layout field, it will check if an item is selected in the Base Layout field. If there is an item selected, it will get the value of the layout field from that item. If there is no base layout selected or if the standard value request is for any other field, the inner standard values provider handles it as normal.

public class BaseLayoutStandardValuesProvider : StandardValuesProvider
{
    private readonly StandardValuesProvider _innerProvider;
    private readonly IBaseLayoutValueProvider _baseLayoutValueProvider;
    private readonly ILog _log;
         
    public BaseLayoutStandardValuesProvider(
        StandardValuesProvider innerProvider,
        IBaseLayoutValueProvider baseLayoutValueProvider,
        ILog log)
    {
        Assert.ArgumentNotNull(innerProvider, "innerProvider");
        Assert.ArgumentNotNull(baseLayoutValueProvider, "layoutValueProvider");
        Assert.ArgumentNotNull(log, "log");
 
        _innerProvider = innerProvider;
        _baseLayoutValueProvider = baseLayoutValueProvider;
        _log = log;
    }
         
    public override string GetStandardValue(Field field)
    {
        try
        {
            if (field.IsLayoutField())
            {
                var layoutValue = _baseLayoutValueProvider.GetBaseLayoutValue(field);
                if (!string.IsNullOrEmpty(layoutValue))
                {
                    return layoutValue;
                }
            }
        }
        catch (Exception ex)
        {
            _log.Error(ex, "Error getting layout value.");
        }
 
        return _innerProvider.GetStandardValue(field);
    }
         
    public override void Initialize(string name, NameValueCollection config)
    {
        _innerProvider.Initialize(name, config);
    }
         
    public override bool IsStandardValuesHolder(Item item)
    {
        return _innerProvider.IsStandardValuesHolder(item);
    }
         
    public override string Name
    {
        get { return _innerProvider.Name; }
    }
         
    public override string Description
    {
        get { return _innerProvider.Description; }
    }
}

public class BaseLayoutValueProvider : IBaseLayoutValueProvider
{
    private readonly string[] _databases;
    private readonly IBaseLayoutValidator _baseLayoutValidator;
    private readonly ILog _log;
         
    public BaseLayoutValueProvider(
        string[] databases,
        IBaseLayoutValidator baseLayoutValidator,
        ILog log)
    {
        Assert.ArgumentNotNull(baseLayoutValidator, "baseLayoutValidator");
        Assert.ArgumentNotNull(log, "log");
 
        _databases = databases;
        _baseLayoutValidator = baseLayoutValidator;
        _log = log;
    }
         
    public virtual string GetBaseLayoutValue(Field field)
    {
        // Sanity check.  Make sure the context is appropriate for
        // attempting to find a base layout.
        if (!field.IsLayoutField()
            || !_databases.Contains(
                field.Item.Database.Name,
                StringComparer.OrdinalIgnoreCase)
            || !field.Item.Paths.IsContentItem
            || !field.Item.HasField(BaseLayoutSettings.FieldId))
        {
            return null;
        }
 
        // Get the item selected in the Base Layout field.  Otherwise, exit.
        var baseLayoutItem = new BaseLayoutItem(field.Item).BaseLayout;
        if (baseLayoutItem == null)
        {
            return null;
        }
 
        // Prevent an infinite loop
        if (_baseLayoutValidator.HasCircularBaseLayoutReference(field.Item))
        {
            _log.Warn(
                string.Format(
                    "Circular base layout reference detected on item {0}.  Aborting resolution of base layouts.",
                    field.Item.ID));
            return null;
        }
 
        // Get the value of the layout field on the base layout.
        // If the selected item also has a base layout selected,
        // this will cause implicit recursion.
        return new LayoutField(baseLayoutItem).Value;
    }
}

Note that the selected base layout item could also have a base layout selected, so it triggers implicit recursion. The provider checks for a circular reference in the chain of base layouts to prevent an infinite loop.

The result is the ability to build a layout hierarchy like this.



A ribbon button and dialog can then be provided in page editor to allow the user to switch between base layouts. Note that if you change an item’s base layout any components with a placeholder path that does not exist on the new base layout will not be visible. Special steps must be taken if using Base Layouts in combination with dynamic placeholders. That is a post for another day.

Note that the code shown above is for the Sitecore 7 version. In my next post I will show what changes are needed to support new versioned Final renderings field in Sitecore 8.

Sitecore development, Sitecore custom code

Comments

Add a Comment

*
*

Please confirm you are human by typing the text you see in this image: