One of the (many) key differentiating factors that sets Sitecore apart from many other CMS platforms is the Experience Editor. The Experience Editor is Sitecore’s WYSIWYG editor, providing content authors the opportunity to edit content within the context of the page, layout the page, etc… It’s quite a powerful tool. All that being said, however, there is always room for improvement. As with everything else within the platform, Sitecore has provided us a method to customize the Experience Editor to make it more useful to content authors.
In the first post of this new series, Taming the Experience Editor, we’re going to look at Placeholders. Placeholders are Sitecore’s way to allow authors to place components (or renderings) in certain places on the page.
A layout designer can specify regions of the page into which authors can place a variety of components. This can be very helpful, but as you start adding more and more components to placeholders on a given page, it becomes confusing where you’re placing what – especially if you’re nesting placeholders.
Take this scenario, for example:
Here, I have added three layers of placeholders to subdivide my page. When I click on Component to pop up the +Add here button, I get some stacking of buttons. It gets even worse if you have small blocks of content! Of course, you can always select the placeholder and use the Parent button to traverse back up the ancestor placeholders/components tree but, visually, it could be better.
A colleague of mine here at RBA, Nick Daigle, implemented a little bit of code on one of his projects to help separate or better define, visually, the layering and hierarchy of components and placeholders. Once we’re done, it’ll look more like this:
Better, right?? Now, you can clearly see the relationship amongst placeholders and content. So, how do we achieve this sorcery?
The code
We start by adding a new class that inherits from Sitecore.Mvc.ExperienceEditor.Presentation.Wrapper.
public class EditorRenderingWrapper : Wrapper { public EditorRenderingWrapper(TextWriter writer, IMarker marker) : base(writer, marker) { } }
Next, we create two new processors that implement RenderRenderingProcessor.
public class AddEditorRenderingWrapper : RenderRenderingProcessor { public override void Process(RenderRenderingArgs args) { if (args.Rendered || Context.Site == null || !Context.PageMode.IsExperienceEditorEditing || args.Rendering.RenderingType == &quot;Layout&quot;) { return; } var marker = GetMarker(args); if (marker == null) { return; } args.Disposables.Add(new EditorRenderingWrapper(args.Writer, marker)); } public IMarker GetMarker(RenderRenderingArgs args) { var renderingContext = RenderingContext.CurrentOrNull; IMarker marker = null; var renderingItem = args.Rendering.RenderingItem; if (renderingItem != null) { marker = new EditorComponentRenderingMarker(renderingItem.Name); } return marker; } } public class EndEditorRenderingWrapper : RenderRenderingProcessor { public override void Process(RenderRenderingArgs args) { foreach (IDisposable wrapper in args.Disposables.OfType<EditorRenderingWrapper>()) { wrapper.Dispose(); } } }
Finally, we create an instance of IMarker. This is what will actually add the chrome around our rendering.
public class EditorComponentRenderingMarker : IMarker { private string _componentName; public EditorComponentRenderingMarker(string componentName) { _componentName = componentName; } public string GetStart() { string formatstring = " <div class=\"component-wrapper {0}\"> <span class=\"wrapper-header\">{1}</span> <div class=\"component-content clearfix\">"; return string.Format(formatstring, _componentName.Replace(" ", string.Empty), _componentName); } public string GetEnd() { return "</div> </div> "; } }
Next, we do a little patching. We want to patch our new processors in after Sitecore.Mvc.ExperienceEditor.Pipelines.Response.RenderRendering.AddWrapper, but before Sitecore.Mvc.Pipelines.Response.RenderRendering.AddRecordedHtmlToCache.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <mvc.renderRendering> <processor patch:after="processor[@type='Sitecore.Mvc.ExperienceEditor.Pipelines.Response.RenderRendering.AddWrapper, Sitecore.Mvc.ExperienceEditor']" type="TamingEE.Cms.Custom.RenderingWrapper.AddEditorRenderingWrapper, TamingEE.Cms.Custom"/> <processor patch:before="processor[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.AddRecordedHtmlToCache, Sitecore.Mvc']" type="TamingEE.Cms.Custom.RenderingWrapper.EndEditorRenderingWrapper, TamingEE.Cms.Custom"/> </mvc.renderRendering> </pipelines> </sitecore> </configuration>
Lastly, we throw in a little CSS and a conditional class to our <body> tag.
body.edit-mode .component-wrapper { border: 2px solid #EEE; border-top: none; box-sizing: border-box; margin: 10px; overflow: hidden; } body.edit-mode .component-wrapper span.wrapper-header { display: block; color: #111; background-color: #EEE; height: 30px; line-height: 30px; padding: 0 10px; font-size: 14px; font-family: Arial, sans-serif; } body.edit-mode .component-wrapper .component-content { padding: 10px; min-height: 50px; } body.edit-mode .component-wrapper .component-content .component-wrapper { margin: 10px 0; } body.edit-mode .mdl-container .mdl-locations-container .scEmptyImage { display: none; }
<body class="@(Sitecore.Context.PageMode.IsPageEditor ? "edit-mode" : string.Empty)"> <h1>@Html.Sitecore().Field("title")</h1> <div> @Html.Sitecore().Placeholder("main")</div> </body>
That’s all there is to it!
All of the source code can be found on github and, as always, I welcome feedback!
Happy Sitecore trails, my friends!
Hi,
Interesting approach and it looks really nice but for me we are in contradiction with the principle of the Experience Editor (WYSIWIG) and it looks more like a “Backend Editor”. Something I would like to see in the upcoming Sitecore version is more a drag & drop system which can resolve the issue of the actual Experience Editor difficulties to really put component at the right place.
But until that, I’ll totally use that kind of system, even if we lose the “wysiwig” as the actual feature is really complex with the kind of website we are doing now ( Atomic component – Grid System – etc).
LikeLike
One question with this…why does it seem to skip every other placeholder? I’d be expecting it to layer on every level to click on, but looking at your placeholder list versus what’s displayed, and trying to implement this myself, it seems to be skipping the odd-numbered holders. Like in mine, I put in a “main”, then it goes to “two column”, then to “left column”, then the module rendering. But header-wise, it just shows “two column” and the module rendering; I’d be expecting something for all levels. Thanks.
LikeLike
Looking over the example here, and implementing it in my code, it seems that the chrome is surrounding the renderings, not the placeholders? I was looking at it where I could click on the chrome and get the placeholder to pop up the “add here” tabs, but when I implemented this, only my renderings get chrome. Can this be modified to affect both placeholders and renderings? Thanks.
LikeLike