Placeholder Settings make the world go round.  Unfortunately, out of the box Placeholder Settings have some limitations.  While it’s true you can create a generic placeholder setting and then override it at the template level.  Sometimes you may have dozens of “common” components that you want on every template.  If you’ve tried to add a generic AND template-specific placeholder setting to a standard values before, you know that Sitecore picks the first one.  It doesn’t have a concept of merging.  If you have a new component that is common to all templates, you need to add it to all template-specific placeholder settings.  This can be a tiring pain, and leaves room for errors.

Enter in: Composite Placeholder Settings.

First, we’re going to create a new template in Sitecore that will inherit from Placeholder Settings.  In this template, we’ll add a new field to allow for the selection of other Placeholder Settings.

TemplateItemCPS

(We’re also going to create a folder template and stick that in the Placeholder Settings Folder).

Now you can insert new Composite Placeholder Settings in the Placeholder Settings folder.

InsertOptionsCPS

Our template functions exactly the same as a normal Placeholder Setting, but has that one additional field, Base Placeholder Settings.

ItemshotCPS

In this example, our Composite Placeholder Setting will inherit all the Placeholder Settings for ‘main 2’ and ‘Main’, as well as the addition of the ‘Staff Listing Accordion’.  How’s the code look for this?  Glad you asked!

Sitecore uses a pipeline called getPlaceholderRenderings which includes a method for returning all the renderings listed in the associated Placeholder Settings.  Here’s a dotPeek view of the method.

protected virtual List<Item> GetRenderings(Item placeholderItem, out bool allowedControlsSpecified)
{
  Assert.ArgumentNotNull((object) placeholderItem, "placeholderItem");
  allowedControlsSpecified = false;
  ListString listString = new ListString(placeholderItem["Allowed Controls"]);
  if (listString.Count <= 0)
	return (List<Item>) null;
  allowedControlsSpecified = true;
  List<Item> list = new List<Item>();
  foreach (string path in listString)
  {
	Item obj = placeholderItem.Database.GetItem(path);
	if (obj != null)
	  list.Add(obj);
  }
  return list;
}

That’s pretty straight forward.  Find the Allowed Controls field and grab all the items inside.  We’re going to leverage this and take it one step further:

namespace Client.Framework.Processors
{
    public class GetAllowedCompositeRenderings : GetAllowedRenderings
    {
        protected override List<Item> GetRenderings(Item placeholderItem, out bool allowedControlsSpecified)
        {
            Assert.ArgumentNotNull(placeholderItem, "placeholderItem");

            var list = base.GetRenderings(placeholderItem, out allowedControlsSpecified);

            var inheritedSettingsField = (MultilistField) placeholderItem.Fields["Base Placeholder Settings"];

            if (inheritedSettingsField != null)
            {
                foreach (var item in inheritedSettingsField.GetItems())
                {
                    list.InsertRange(0, base.GetRenderings(item, out allowedControlsSpecified));
                }
            }

            //We don't want any duplicates
            return list.Distinct().ToList();
        }
    }
}

All we’ve done here is inherit from Sitecore’s default method, and overridden the GetRenderings.  We use the Sitecore method to grab any renderings for the Composite, and then iterate through all base settings and merge it into a list. Finally, we return just the distinct values back.  That’s not bad at all.

Here’s the patch to replace the default processor.

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <getPlaceholderRenderings>
        <processor type="Client.Framework.Processors.GetAllowedCompositeRenderings, Client.Framework" patch:instead="processor[@type='Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings, Sitecore.Kernel']"/>
      </getPlaceholderRenderings>
    </pipelines>
  </sitecore>
</configuration>

There you have it.  If there are any questions about this, drop a comment!