Reset the placeholder's layout in Sitecore

28 February 2012
Przemek Taront
Frink_Cognifide_2016_HeaderImages_0117

This post concerns Sitecore 6.5.0 (rev. 111123).

In my recent post Complex layouts in Sitecore using standard values hierarchy, I presented a solution of how to split layout definition across hierarchy of templates. Having this new approach I wanted to refactor my site and move some parts of the layout to Sitecore template definitions.

First I took header and footer renderings from my home page and moved them into my base template. In the next step I wanted to reset the header and footer on my home page to make sure that any future changes made to the template will not be overriden in my home page. But Sitecore gave my only one option, the reset layout button in the ribbon: Reset layout button in the ribbon

 But this command resets the entire layout.  However, I need to reset only selected areas of my page. Let's add a new command to the floating toolbar around placeholder in page editor, that can reset presentation for the given placeholder.

Implementation

My new command is going to extented WebEditCommand class. Unfortunately CommandContext passed to the Execute method is missing the name of the current placeholder. Let's fix that by adding the placeholder in the GetChromeData pipeline:
public class GetPlaceholderKeyChromeData : GetChromeDataProcessor
{
  public override void Process(GetChromeDataArgs args)
  {
    if ("placeholder".Equals(args.ChromeType, StringComparison.OrdinalIgnoreCase))
    {
      string placeHolderKey = args.CustomData["placeHolderKey"] as string
      args.CommandContext.Parameters.Add("placeHolderKey", placeHolderKey);
    }
  }
}
Now it is time to write the actual command class. After ensuring that any pending changes have been saved, prompt the user with a dialog if he or she really want to reset the placeholder:
public class ResetPlaceholderCommand : WebEditCommand
{
  public override void Execute(CommandContext context)
  {
    NameValueCollection parameters = new NameValueCollection();
    parameters["placeHolderKey"] = context.Parameters["placeHolderKey"];
    parameters["items"] = SerializeItems(context.Items);
    Context.ClientPage.Start(this, "Run", parameters);
  }

  protected void Run(ClientPipelineArgs args)
  {
    if (SheerResponse.CheckModified())
    {
      if (args.IsPostBack)
      {
        if (args.Result == "yes")
        {
          Item item = DeserializeItems(args.Parameters["items"])[0];
          Reset(item, args.Parameters["placeHolderKey"]);
        }
      }
      else
      {
        SheerResponse.Confirm("Are you sure you want to reset the placeholder?");
        args.WaitForPostBack();
      }
    }
  }
}
The core algorithm plays with XML in the layout fields. The logic goes as follows: take standard values, iterate over renderings in the given placeholder, look for the same rendering in the current item and override the value (if it has been changed) or re-add (if it has been removed).
private static void Reset(Item item, string placeHolderKey)
{
  ID layoutId = item.Visualization.Layout.ID;
  LayoutField field = item.Fields[FieldIDs.LayoutField];
  XDocument stdValXml = XDocument.Load(new StringReader(GetStandardValue(field)));
  XDocument itemXml = XDocument.Load(new StringReader(field.Value));

  XElement stdValLayout = stdValXml.Root.Elements("d")
      .First(d => GetAttrib(d, "l") == layoutId.ToString());
  XElement itemLayout = itemXml.Root.Elements("d")
      .First(d => GetAttrib(d, "l") == layoutId.ToString());
  IList<XElement> renderings = stdValLayout.Elements("r")
      .Where(r => GetAttrib(r, "ph") == placeHolderKey).ToList();

  foreach (XElement rendering in renderings)
  {
    XElement newRendering = itemXml.Root.Descendants("r")
        .FirstOrDefault(r => GetAttrib(r, "uid") == GetAttrib(rendering, "uid"));
    if (newRendering != null)
    {
      newRendering.ReplaceWith(rendering);
    }
    else
    {
      itemLayout.Add(rendering);
    }
  }

  item.Editing.BeginEdit();
  string newFieldValue = itemXml.ToString(SaveOptions.DisableFormatting);
  LayoutField.SetFieldValue(field.InnerField, newFieldValue);
  item.Editing.EndEdit();
}
Note that snippets above skip null checks and error handling. To get the full code, including missing helper functions, download the source using the link below.

Configuration

To make everything work you need to add the following to Sitecore configuration:
<pipelines>
  <getChromeData>
    <processor patch:before="*[@type='Sitecore.Pipelines.GetChromeData.GetPlaceholderChromeData, Sitecore.Kernel']"
      type="Cognifide.SitecoreExtension.ResetRenderings.GetPlaceholderKeyChromeData, Cognifide.SitecoreExtension.ResetRenderings"/>
  </getChromeData>
</pipelines>
<commands>
  <command name="webedit:resetplaceholder"
    type="Cognifide.SitecoreExtension.ResetRenderings.ResetPlaceholderCommand, Cognifide.SitecoreExtension.ResetRenderings"/>
</commands>
Now let's add the command to the core database:
   Reset placeholder button definition in core database  
And try out the new command:
   Reset placeholder button in the floating toolbar
Once you click the recycle icon, the layout of this placeholder will be reverted to what has been defined in the template. Note, that in some cases after reset the order of renderings in the placeholder might be messed up. However, you can easily fix it in the page editor.

Download source

Download package

to use with Installation Wizard from Sitecore Desktop

If you have any comments to my post, please feedback below.