Adding Sections to the Sitecore Content Editor

It can be useful to add a custom section to the content editor, especially when you want to give the content author a better experience. In this case, we’ll be adding a new section to the content editor to provide the content author with a friendly url of the item the author is working on.

If we take a look at the …/sitecore/admin/showconfig.aspx we can find where the pipelines are configured for rendering the content editor.

With that found, let’s head over to the Sitecore.Client.dll to take a look at what’s happening. You’ll notice that the process method is accepting the pipeline arguments.

Look further down in the class. There is a lot happening. We are interested in is the RenderSection method.

There are 3 important methods being called here: RenderSectionBegin, RenderFields, RenderSectionEnd. These methods are responsible adding the new section to our content editor page. If you look at the RenderSection method in the EditorFormatter.cs class, you’ll see an example of how these are used.

Taking a cue from the EditorFormatter, let’s make our own class.

using System;
using System.Linq;
using System.Text;
using Sitecore.Data.Items;
using Sitecore.Links;
using Sitecore.Shell;
using Sitecore.Shell.Applications.ContentEditor.Pipelines.RenderContentEditor;
using Sitecore.Sites;
using Sitecore.Text;
using Sitecore.Web;
using Sitecore.Web.UI.HtmlControls;

namespace SampleSite.UrlSectionRenderer
{
    public class UrlSectionRenderer
    {
        private static readonly string SectionName = "Page Url Section";

        public void Process(RenderContentEditorArgs args)
        {
            string sectionId = "UrlSectionRenderer";
            string sectionIcon = "Core3/16x16/home_yellow.png";

            Item item = args.Item;
            if (item != null && item.Paths.IsContentItem && HasLayout(item))
            {
                bool shouldRenderDetails = !this.IsSectionCollapsed() || UserOptions.ContentEditor.RenderCollapsedSections;

                args.EditorFormatter.RenderSectionBegin(
                    args.Parent,
                    sectionId,
                    SectionName,
                    SectionName,
                    sectionIcon,
                    this.IsSectionCollapsed(),
                    UserOptions.ContentEditor.RenderCollapsedSections);

                if (shouldRenderDetails)
                {
                    this.RenderField(args, item);
                }

                args.EditorFormatter.RenderSectionEnd(args.Parent, shouldRenderDetails, this.IsSectionCollapsed());
            }
        }

        private void RenderField(RenderContentEditorArgs args, Item item)
        {
            var sb = new StringBuilder();
            var itemUrl = GetItemUrl(item);

            sb.Append("<table class='scEditorQuickInfo'>");
            sb.Append("<colgroup>");
            sb.Append("<col style='white-space:nowrap' valign='top'>");
            sb.Append("<col style='white-space:nowrap' valign='top'>");
            sb.Append("</colgroup>");
            sb.Append("<tr>");
            sb.Append("<td>Page URL:</td>");
            sb.AppendFormat("<td><a href='{0}' target='_blank'>{0}</a></td>", itemUrl);
            sb.Append("</tr>");
            sb.Append("</table>");

            args.EditorFormatter.AddLiteralControl(args.Parent, sb.ToString());
        }

        private bool IsSectionCollapsed()
        {
            bool collapsedDefault = true;
            UrlString collapsedSections = new UrlString(Registry.GetString("/Current_User/Content Editor/Sections/Collapsed"));
            string collapsed = collapsedSections[SectionName];

            if (string.IsNullOrEmpty(collapsed))
            {
                return collapsedDefault;
            }

            return collapsed == "1";
        }

        private string GetItemUrl(Item item)
        {
            var options = new UrlOptions
            {
                AlwaysIncludeServerUrl = true,
                Language = item.Language,
                LanguageEmbedding = LanguageEmbedding.Never,
                LowercaseUrls = true,
                AddAspxExtension = false,
                EncodeNames = true,
                Site = GetSite(item)
            };

            return LinkManager.GetItemUrl(item, options);
        }

        private bool HasLayout(Item item)
        {
            return item?.Visualization?.Layout != null;
        }

        private SiteInfo GetSiteInfo(Item item)
        {
            return Sitecore.Configuration.Factory.GetSiteInfoList()
                .FirstOrDefault(x => x.Database != "Core"
                                     && x.Language == item.Language.Name
                                     && !string.IsNullOrWhiteSpace(x.RootPath)
                                     && item.Paths.FullPath.StartsWith(string.Concat(x.RootPath, x.StartItem), StringComparison.InvariantCultureIgnoreCase));
        }

        private SiteContext GetSite(Item item)
        {
            var site = GetSiteInfo(item);

            return site != null
                ? new SiteContext(site)
                : null;
        }
    }
}

The first method we start with is the Process method. This is what the pipeline will call first. In that method, we are then making the method calls for the RenderSectionBegin, RenderField, and RenderSectionEnd.

Following those are a few methods I’ve added here for descriptive purposes but they should be extension methods of an item. Those are the HasLayout, GetSiteInfo, and GetSite methods. Below is the configuration you’ll need to get this wired up.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <pipelines>
      <renderContentEditor>
        <processor type="SampleSite.UrlSectionRenderer" patch:before="processor[@type='Sitecore.Shell.Applications.ContentEditor.Pipelines.RenderContentEditor.RenderSkinedContentEditor, Sitecore.Client']" />
      </renderContentEditor>
    </pipelines>
  </sitecore>
</configuration>

Here is the result. Clicking on the link will open the item’s page in a new tab.

About Phil Paris

Hi, my name is Phil Paris and I’m a Sitecore Architect and general Sitecore enthusiast. I’ve been working with Sitecore since 2013. Through this blog I will be sharing Sitecore insights, tips and tricks, best practices and general knowledge with the hopes to further the community at large. Please feel free to reach out to me at any time!

View all posts by Phil Paris →