Loading...
Sitecore

Include additional renderings into layout definition of Sitecore items

While extending Sitecore default mechanism of getting Presentation Details, which was explained in previous post, we can go one step further.


The idea

Imagine that there is a site structure already set. Pages of different templates have some components in Presentation Details. There is this situation that Client need to add a Search box on every page.

There is a possibility to extend layout with new rendering without changing Presentation Details of every item. We just need to tell Sitecore to read Presentation Details not only from the current item, but extend it with custom code which will add extra components from the Layout Extensions Page. This blog post explains how we can achieve it.

Create new empty page type template Layout Extensions Page just for distinction of other Page Types templates. Than create Extensions Page item and add Search component to its Presentation Details.

Now create Layout Extensions Template with the special field Layout Extensions Source of Droptree type.

Assuming that there is a Base Page template from which all Page Types templates inherits,  add  Layout Extensions Template  to Base template field of Base Page template item.

On _Standard Values (of Base Page template) set value of Layout Extensions Source  field to Extensions Page item (the one with Search component).


Implementation

Modification of mvc.getXmlBasedLayoutDefinition pipeline is needed. For that we need to create class that inherits from GetFromLayoutFiels and override Process method as follows:

public class GetFromLayoutExtension : GetFromLayoutField
    {
        public override void Process(GetXmlBasedLayoutDefinitionArgs args)
        {
            if (args.Result == null)
                return;

            Item item = args.ContextItem ?? PageContext.Current.Item;
            if (item == null || Context.Device == null)
                return;

            if (string.IsNullOrEmpty(item["Layout Extensions Source"]))
                return;

            var extensionRenderingsList = GetExtensionRenderings(item);
            if (extensionRenderingsList == null)
                return;

            var currentItemLayoutFieldValue = LayoutField.GetFieldValue(item.Fields[FieldIDs.FinalLayoutField]);
            if (currentItemLayoutFieldValue.IsEmptyOrNull())
                return;

            var itemDevice = args.Result.Elements(XName.Get("d"))
                .FirstOrDefault(d => d.GetAttributeValueOrEmpty("id") == Context.Device.ID.ToString());

            if (itemDevice == null)
                return;
            foreach (var xElement in extensionRenderingsList)
                itemDevice.Add(xElement);
        }

        private List<XElement> GetExtensionRenderings(Item item)
        {
            var extensionsItem = item.Database.GetItem(new ID(item.Fields["Layout Extensions Source"].Value));
            var layoutFieldValue = LayoutField.GetFieldValue(extensionsItem.Fields[FieldIDs.FinalLayoutField]);
            var extensionsItemDevice = XDocument.Parse(layoutFieldValue).Root.Elements(XName.Get("d")).
                FirstOrDefault(d => d.GetAttributeValueOrEmpty("id") == Context.Device.ID.ToString());
            if (extensionsItemDevice == null)
                return null;
            return extensionsItemDevice.Elements(XName.Get("r")).ToList() ?? null;
        }
    }

Description

After checking all necessary null conditions grab current item. Check if “Layout Extensions Source” field is not empty. This is the field which supposed to have extensions item ID.

If there is a value –  GetExtensionRenderings method is invoked, where value of  Layout Extensions Source  field is used to  get extensions item.

From extensions item,  string layout definition is taken using GetFieldaValue of Sitecore LayoutField class. Then, it is parsed to xml and extensions item device definition. This definition contains components that we want to extend with (in our case – Search component).

Once we have the list of extending components extensionRenderingsList, we need to check if current item has layout definition.

var currentItemLayoutFieldValue = LayoutField.GetFieldValue(item.Fields[FieldIDs.FinalLayoutField]);
            if (currentItemLayoutFieldValue.IsEmptyOrNull())
                return;

Than we grab current item’s device xml definition where all components are defined, check if it’s not null and add every extension component from the list extensionRenderingsList.

Current item layout definition now contains list of previously defined components and extending ones.

Finally we have to register our processor, this time after Sitecore.Extend.Layout.Processors.GetFromLayoutExtension.

<pipelines>
      <mvc.getXmlBasedLayoutDefinition>
        <processor type="Sitecore.Extend.Layout.Processors.GetFromLayoutExtension, Sitecore.Extend.Layout" patch:after="processor[@type='Sitecore.Mvc.Pipelines.Response.GetXmlBasedLayoutDefinition.GetFromLayoutField, Sitecore.Mvc']" />
      </mvc.getXmlBasedLayoutDefinition>
    </pipelines>

Summary

Using this particular pipeline causes this to happen during rendering page process. Therefore we only need to change one item (Extensions Page) and changes are visible on all pages that contain a link to this item.


Thank you for reading. Maybe you will find my other blog post Override Sitecore default mechanism of getting Presentation Details interesting as well.

Leave a Reply

Your email address will not be published. Required fields are marked *