Dynamic content editor templates using Rendering Dependencies

21 December 2012 at 00:00 by Ruud van Falier - Post a comment

In Sitecore, you will often have items that use the same template, but differ in layout settings.
For example, when you have a content page (title, introduction, body) that can be extended with a related downloads or related faq component using the Page Editor.
In such cases I will usually add a treelist field for those related items to the basic content page.

I also like to use template inheriting a lot.
So I will make a basic content data template and use that same template as base for page template such as content page, news page or event page.
In the case of news page and event page I need an extra field, content date, that represents the news or event date.
Taking the easy way out, I will also put this field on the basic content data template.

This has one major drawback; the fields will always be there, even if they are not used.
So the content page will have the content date field even though we don't need it, as goes for the the related downloads field which will be there if that component is not present.
To most developer this may not seem like a problem, but for content editors or less advanced users this can be very confusing!

I thought about this problem after reading a StackOverflow question that mentioned this method and I came up with a solution for it.
I've added a field to the Template Field and Template Section templates that allows you to select Renderings that are required to be set in order for that field or section to be visible in the content editor.
The term I came up with for this is Rendering Dependencies.

If we take the content date field as an example.
We set its rendering dependencies to be the news content and event content sublayout.
From now on, that field will only be visible if the item that uses that field has those sublayout set to its presentation details.
The same principle applies to template sections.
We can set the related downloads section to be depended on the downloads sublayout.

Let's take a look at how this is done:

Modify the Template Field system template

Modify the Template Section system template

Now we can set up the rendering dependencies in our templates:

Set up your field dependencies

Set up your section dependencies

Okay, now we need some code that puts it all together.
Sitecore provides the <getContentEditorFields> pipeline that is used by the Content Editor to determine which fields can be displayed.
I created an overload for the GetField processor.

Use the following configuration to replace Sitecore's default processor with our own:

<pipelines>
  <getcontenteditorfields>
    <processor patch:after="processor[@type='Sitecore.Shell.Applications.ContentEditor.Pipelines.GetContentEditorFields.GetFields, Sitecore.Client']" type="ParTech.Demo.Pipelines.GetContentEditorFields.GetFields, ParTech.Demo" />
    <processor type="Sitecore.Shell.Applications.ContentEditor.Pipelines.GetContentEditorFields.GetFields, Sitecore.Client">
      <patch:delete />
    </processor>
  </getcontenteditorfields>
</pipelines>

Here is the code for the pipeline processor:

using System.Collections.Generic;
using System.Linq;
using ParTech.Demo.Extensions;
using Sitecore.Data;

namespace ParTech.Demo.Pipelines.GetContentEditorFields
{
  public class GetFields : Sitecore.Shell.Applications.ContentEditor.Pipelines.GetContentEditorFields.GetFields
  {
    protected override bool CanShowField(Sitecore.Data.Fields.Field field, Sitecore.Data.Templates.TemplateField templateField)
    {
      bool canShowField = base.CanShowField(field, templateField);

      if (canShowField)
      {
        // Determine which renderings are required to be set on the presentation details for this field to be visible
        IEnumerable<ID> fieldDependencies = templateField.GetRenderingDependencies();

        if (fieldDependencies.Count() > 0)
        {
          // This field has dependencies and will be hidden until it's verified that it's okay to show it
          canShowField = false;

          // Determine which renderings are set on the presentation details of the item that is being edited
          List<ID> itemRenderings = field.Item.GetRenderings();

          // Find out if this field matches the necessary dependencies
          foreach (ID fieldDependency in fieldDependencies)
          {
            if (itemRenderings.Contains(fieldDependency))
            {
              return true;
            }
          }
        }
      }

      return canShowField;
    }
  }
}

Note that I've used a few extension methods to keep the code clean.

The first one is an extension method for the Item class that we use to retrieve the configured Renderings of the item:

using System.Collections.Generic;
using System.Linq;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Layouts;
using Sitecore.Data.Fields;

namespace ParTech.Demo.Extensions
{
  public static class ItemExtensions
  {
    /// <summary>
    /// Get the renderings that are configured in the presentation details of the specified item
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    public static List<ID> GetRenderings(this Item item)
    {
      var renderings = new List<ID>();

      string layoutXml = LayoutField.GetFieldValue(item.Fields[Sitecore.FieldIDs.LayoutField]);
      var layout = LayoutDefinition.Parse(layoutXml);

      foreach (DeviceDefinition device in layout.Devices)
      {
        foreach (RenderingDefinition rendering in device.Renderings)
        {
          renderings.Add(ID.Parse(rendering.ItemID));
        }
      }

      return renderings;
    }

  }
}

The second one is an extension method for the TemplateField class that we use to retrieve the configured Rendering Dependencies for that field:

using System.Collections.Generic;
using System.Linq;
using System.Collections;
using Sitecore.Data;
using Sitecore.Data.Templates;
using Sitecore.Data.Items;

namespace ParTech.Demo.Extensions
{
  public static class TemplateFieldExtensions
  {
    /// <summary>
    /// Get the renderings that this template field or its section depends on
    /// </summary>
    /// <param name="templateField"></param>
    /// <returns></returns>
    public static IEnumerable<ID> GetRenderingDependencies(this TemplateField templateField)
    {
      IEnumerable<ID> fieldDependencies = GetRenderingDependencies(templateField.ID);
      IEnumerable<ID> sectionDependencies = GetRenderingDependencies(templateField.Section.ID);

      return fieldDependencies.Union(sectionDependencies);
    }

    private static IEnumerable<ID> GetRenderingDependencies(ID itemId)
    {
      Database database = Sitecore.Context.ContentDatabase;

      if (database != null)
      {
        Item fieldItem = database.GetItem(itemId);

        if (fieldItem != null)
        {
          string dependencies = fieldItem["Depends on renderings"];

          if (!string.IsNullOrEmpty(dependencies))
          {
            return dependencies.Split('|').Select(x => ID.Parse(x));
          }
        }
      }

      return new ID[] { };
    }
  }
}

And finally, the result!
I've created 5 demo items that each have different presentation details settings.
The screenshot shows the presentation details and the content editor for each item.

As you can see, the Content Editor will now only display those fields and sections that are relevant to that specific item (based on its presentation details).
Let me know what you think about it!

At this point I don't have an easy download for this solution, but I will turn it into a shared source module and upload it to the market place.
Once that is done, this blog post will be updated with a link.

Latest