Atomic Design – Engineering Challenge

The header and footer are possibly the most important renderings on your site. Every visitor notices them. Keep reading through this post and see how we built ours using atomic design and are able to perform some magic behind the scenes!

The Header and Footer Project

A couple months ago we started work on our new header and footer. Typically when you are building these components you bind them directly to the layout file and not to a placeholder. This is because you want them to show on every page that utilizes that layout file. You don’t want to set the header and footer directly into the standard values, nor do you want the content authors to manually add it to each and every page. When you bind directly without using a placeholder it looks like this:

@Html.Sitecore().Rendering(@Model.HeaderRenderingId)

Binding a rendering directly to the layout file (or any other component for that matter) you lose much of Experience Editor’s core functionality for that HTML. Mainly AB testing and personalization. For me this is a huge loss for not only our business but any business that goes down this path. This is high value real estate where you need that functionality. Binding to the layout file directly is the norm but with this engineering challenge I can see a major shift.

*Remember when you save an item’s presentation, it is saved directly to that item’s Layout field as XML.  That XML describes which renderings are on the page, which datasources they are linking to, even information regarding AB testing and personalization is stored there.

Here is an image of what our header and footer currently looks like:

atomicDesign-headerfooter

Technical Challenges

With the header and footer projects I set forth technical challenges for my team to work on.

  • Header and footer needs to be built out using atomic design so we can have control at the smallest level.
  • They cannot be statically bound to the layout, it needs to be bound to a placeholder.
  • If I make a change to these renderings in Experience Editor, the change should be reflected on all pages.
    • The header and footer are renderings that persist across all pages, it wouldn’t make sense to update the header and footer on one page and not the other.
    • Also we don’t want our content authors having to make the change to each and every page.  That is needless work, make the change once and it should be reflected across the site.
  • Adding the header and footer to the standard values of all our templates is not going to work and will introduce discrepancies.

The Answer!

We were able to tackle all of these challenges and came up with a straight forward, simple solution. We have added a “header” and “footer” placeholder to our layout file and have built out our atomically designed header and footer on our homepage. This means the renderings that make up our header and footer are bound to those new placeholders and that presentation data is stored as XML in the homepage’s Layout field.

What we do from here is we tap into Sitecore’s rendering process, specifically the “mvc.getXmlBasedLayoutDefinition” pipeline (for non mvc sites it is the “getXmlBasedLayoutDefinition” pipeline) and replace the GetFromLayoutField processor with our own.

Sitecore’s GetFromLayoutField processor simply looks at that item’s layout field, turns the XML into an XElement object and continues down the pipeline. We do the same thing but add some additional functionality. We say, if you are on the homepage continue on as normal because it already has the header and footer. Outside of that (any other page), we grab our homepage item, extract it’s header and footer parts of the XML and then add it to the current item’s xml (we do not save to the item, simply we are modifying the XElement object in memory). From there we let Sitecore continue on as normal. That’s it, super simple. Check out the code below if you don’t believe me 🙂

This allows for us to atomically build out the header and footer, we can bind it to a placeholder, we have AB testing, we have personalization, we make a change directly on the homepage and we can see it reflected immediately on other pages. We did performance evaluation against this and the cost is 22 milliseconds. Also I should note, any changes to the header and footer is done on the homepage, we have modified the Sitecore javascript file that controls the placeholder in Experience Editor. We are basically shutting off this functionality for all pages that are not the homepage thus forcing the content authors to make the change on the homepage and alleviating some confusion. This is something we had to train the content authors with. We made the change in PlaceholderChromeType.js to the onShow method, the code is shown below. (/sitecore/shell/applications/page modes/chrometypes/PlaceholderChromeType.js).

I would love to hear some comments on this and how other people are tackling this type of challenge.
Thanks!
The Collette Web Team

<mvc.getXmlBasedLayoutDefinition>
	<processor patch:instead="processor[@type='Sitecore.Mvc.Pipelines.Response.GetXmlBasedLayoutDefinition.GetFromLayoutField, Sitecore.Mvc']" type="Collette.Library.CustomSitecore.Pipelines.GetXmlBasedLayoutDefinition.GetFromLayoutField, Collette.Library"/>
</mvc.getXmlBasedLayoutDefinition>
			
public class GetFromLayoutField : GetXmlBasedLayoutDefinitionProcessor
{
	public override void Process(GetXmlBasedLayoutDefinitionArgs args)
	{
		if (args.Result == null)
		{
			XElement content = this.GetFromField();
			if (content != null)
			{
				Item item = PageContext.Current.Item;
				if (item != null && !item.IsOfTemplate(HomeItem.TemplateId))
				{
					Log.Debug(string.Format("GetFromLayoutField - Process : Not on homepage"));
					Item homeItem = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.StartPath);
					if (homeItem != null)
					{
						Field homePageLayoutField = homeItem.Fields[FieldIDs.LayoutField];
						if (homePageLayoutField != null)
						{
							string fieldValue = LayoutField.GetFieldValue(homePageLayoutField);
							if (!fieldValue.IsWhiteSpaceOrNull())
							{
								XElement homePageLayout = XDocument.Parse(fieldValue).Root;
								XElement dXElement = content.Element("d");
								if (dXElement != null && homePageLayout != null)
								{
									XElement dXElementInHomePageLayout = homePageLayout.Element("d");
									if (dXElementInHomePageLayout != null)
									{
										dXElement.Add(ExtractContentsFromLayout(dXElementInHomePageLayout.Elements(), "Header"));
										dXElement.Add(ExtractContentsFromLayout(dXElementInHomePageLayout.Elements(), "Footer"));
									}
								}
							}
						}
					}
				}
			}

			args.Result = content;
		}
	}

	protected virtual XElement GetFromField()
	{
		Item item = PageContext.Current.Item;
		if (item == null)
		{
			return null;
		}
		Field field = item.Fields[FieldIDs.LayoutField];
		if (field == null)
		{
			return null;
		}
		string fieldValue = LayoutField.GetFieldValue(field);
		if (fieldValue.IsWhiteSpaceOrNull())
		{
			return null;
		}
		return XDocument.Parse(fieldValue).Root;
	}

	private List<XElement> ExtractContentsFromLayout(IEnumerable<XElement> layoutElements, string placeholder)
	{
		Log.Debug(string.Format("GetFromLayoutField - Process : Starting to extract for placeholder {0}:", placeholder));

		List<XElement> elements = new List<XElement>();

		if (layoutElements.Any())
		{
			foreach (XElement element in layoutElements)
			{
				if (element.HasAttributes)
				{
					if (element.Attribute("ph") != null)
					{
						string value = element.Attribute("ph").Value;
						if (!string.IsNullOrEmpty(value) && (value.StartsWith("/" + placeholder) || value.Equals(placeholder)))
						{
							elements.Add(element);
						}
					}
				}
			}
		}
		Log.Debug(string.Format("GetFromLayoutField - Process : Done with extract"));

		return elements;
	}
}
onShow: function() {
	if (!this._insertionEnabled) {
		return;
	}

	if (this.isReadOnly()) {
		return;
	}

	var placeholderKey = this.placeholderKey();
	if (placeholderKey != null && placeholderKey.indexOf("/Header/") != 0 && placeholderKey.indexOf("/Footer/")!= 0 && placeholderKey !== "Header"
		&& placeholderKey !== "Footer") {
		this.inserter = new Sitecore.PageModes.ChromeTypes.PlaceholderInsertion(this.chrome);
		this.inserter.activate();
	}
	else {
		//any of our site ids
		var siteItemIds = ['{08F455E9-0000-0000-0000-78F2C37AC6F9}', '{110D559F-0000-0000-0000-8A5DF7E70EF9}', '{1FDE838E-0000-0000-0000-745443B12516}', '{9EE170E8-0000-0000-0000-59DF98C9D227}'];
		for (var i = 0; i < siteItemIds.length ; i++) {
			var id = siteItemIds[i];
			if (this.chrome.data.contextItemUri) {
				if (this.chrome.data.contextItemUri.indexOf(id) > -1) {
					this.inserter = new Sitecore.PageModes.ChromeTypes.PlaceholderInsertion(this.chrome);
					this.inserter.activate();
				}
			}
		}
	}
}
			

4 thoughts on “Atomic Design – Engineering Challenge

  1. Pingback: SUGCON Europe 2016: An Insider Look – Part 1 | Sitecoring since 2011

  2. Alexander Smagin

    Good approach.

    You could also create a snippet. It uses a similar approach to what you described – renders content from a specific placeholder of an item. However, the difference is that a snippet is a control that you could put on a page, you could define target item as a data source and in some implementations – placeholder to get content from. Rest is the same.

    Such control would give you more flexibility, rather than just a hard reference to the Home item.

    Reply
  3. venkata phani abburi

    well what is the issue adding placeholders to layout and common renderings to __StandardadValues? Only issue I can think of is with the LayoutDeltas and content editor editing header on a home/child page

    Reply
    1. tbraga1983 Post author

      With Atomic Design layout deltas can cause a slight performance impact and I wouldn’t want to add the header and footer to every page template I have. The header and footer are atomic so you are adding dozens of renderings not just one header component. If you are not going atomic than this would be easier. Like many things there are multiple approaches and you just need to find what is best for your business case. We have a page for the header and a page for the footer and we are dynamically adding those to a placeholder behind the scenes (basically on the fly copying the header and footer layout xml and adding it to the page being rendered). We found this approach is best for us.

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s