When you working in Experience Editor (previously Page Editor) it is quite difficult to understand the hierarchy of controls and their relation to each other, moreover, it is not always clear where your control ends and placeholder starts. Why not add some wrappers around controls? Let’s see how we can do this.

Approach

We need to create a pipeline processor that will inject some markup around the component if the page is in editing mode.

renderRendering processor

Of cause, you could add some conditional elements directly into your views, but in this case, it would be difficult to maintain and keep them consistent when the project will grow.

First of all, we need to understand how Sitecore renders controls. As usual, we need to look through pipelines - one that we need is mvc.renderRendering which you can find in Sitecore.Mvc.config. We should also take a look at Sitecore.MvcExperienceEditor.config as it adds AddWrapper processors (what a nice coincident =) to the pipeline we are interested in. The main goal of this processor is to add ChromeData to each control, which will be used by Experience Editor scripts.

AddWrapper processor

AddWrapper does the following:

  1. Check PageMode - this is only an extension for Editor, therefore it returns null in all other cases.
  2. Create required Marker (a class that implements IMarker interface and return start and end tags for injection)
  3. Create an instance of disposable class Wrapper and add it to pipeline arguments (args.Disposables) - this is the most tricky part.

When *Wrapper *constructor is called it writes start tag from Marker to rendering output stream. End tag should be added after generation of controls markup - it happens when the pipeline is finished and method Dispose called on the arguments object. The method iterated over args.Disposables and calls Wrapper Dispose, which renders end tag.

Implementation

Bearing in mind everything above we need to create following processor:

public class AddColumnWrapper : ExecuteRenderer
{
    public override void Process(RenderRenderingArgs args)
    {
        if (args.Rendered || Context.Site == null || !Context.PageMode.IsPageEditorEditing || args.Rendering.RenderingType == "Layout")
        {
            return;
        }

        var marker = this.GetMarker(args);
        if (marker == null)
        {
            return;
        }

        var index = args.Disposables.FindIndex(x => x.GetType() == typeof(Wrapper));
        if (index < 0) index = 0;
        args.Disposables.Insert(index, new Wrapper(args.Writer, marker));
    }

    protected virtual IMarker GetMarker(RenderRenderingArgs args)
    {
        // logic to create your Marker goes here
    }
}

It would check the required condition, create a marker and wrapper and add them to arguments. Important here (selected) is that marker should be added before other markers to generate closing tag incorrect order (last-in-first-out)

Bugs

And here we come to a bug in Sitecore.Mvc.ExperienceEditor.Pipelines.Response.RenderRendering.AddWrapper processor, which just add a wrapper to the end of a Disposables (which results in first-in-first-out rendering).

1
2
3
...
args.Disposables.Add((IDisposable) new Wrapper(args.Writer, marker));
...

Unfortunately, you won’t be able to find this bug until you tried to add a custom wrapper, which I did.

Configuration

<pipelines>
  <mvc.renderRendering>
    <processor patch:before="processor[@type='Sitecore.Mvc.ExperienceEditor.Pipelines.Response.RenderRendering.AddWrapper, Sitecore.Mvc.ExperienceEditor']"
                type="Example.Pipelines.Response.RenderRendering.AddColumnWrapper, Example" />
    <processor patch:after="processor[@type='Sitecore.Mvc.ExperienceEditor.Pipelines.Response.RenderRendering.AddWrapper, Sitecore.Mvc.ExperienceEditor']"
                type="Example.Pipelines.Response.RenderRendering.AddComponentWrapper, Example" />
    <processor patch:instead="processor[@type='Sitecore.Mvc.ExperienceEditor.Pipelines.Response.RenderRendering.AddWrapper, Sitecore.Mvc.ExperienceEditor']"
                type="Example.Pipelines.Response.RenderRendering.AddWrapperFix, Example" />
  </mvc.renderRendering>
</pipelines>

In my case, there were 2 wrappers: one to change the size of control (according to grid columns) and the second to add a markup, while selected lines represent fix for the Experience Editor library.

Results

Wrappers rendered around components in Experience editor

Finally, that’s how my components look now.


Follow me on Twitter @true_shoorik. Share if the post was useful for you.