Limit child template types in Sitecore

In this example, we are examining a scenario where an item in the Sitecore content tree is allowed to have only 1 item of a certain type as a child. This is essentially restricting the author from creating multiple child items of the same type.

In the below images, we have Sample Page 1 with a child item of Sample Sub Item. If the author tries to add another Sample Sub Item under Sample Page 1, they will be prompted with a message and the item creating will not take place.

The configuration

In the configuration below, under the item:saved event, we define the handler type attribute. Under that, there is a config node with a hint=”raw:Add”. Sitecore allows you extend the configurations and will use reflection when the handler is invoked to find and execute that method. When that method is called, it will pass the child configuration elements to the method.

The <enforceUniqueTemplateParams> element has 3 child elements. <parentTemplateId> is the template ID that of the template that the restriction on it. <childTemplateId> is the template ID that there can only be one of. <errorMessage> is the text that will be displayed in the Sheer Response to the client if a second child item is added.

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event name="item:saved">
        <handler type="SampleSite.EventHandlers.EnforceUniqueTemplateOnItemSavedHandler, SampleSite" method="OnItemSaved">
          <config hint="raw:Add">
            <enforceUniqueTemplateParams>
              <!-- Sample Page can only have 1 Sample Sub Item -->
              <parentTemplateId>BAB84BFD-9982-4C69-82A7-678024DE24C7</parentTemplateId>
              <childTemplateId>867C5E50-8910-4EDC-88F5-DA9A1C32D391</childTemplateId>
              <errorMessage>A sample page may only have 1 sample sub item.</errorMessage>
            </enforceUniqueTemplateParams>
            <enforceUniqueTemplateParams>
              <!-- Sample Sub Item can only have 1 Sub Sub Item -->
              <parentTemplateId>74C6BA54-9CC3-477B-9BB2-5FB40D228B2E</parentTemplateId>
              <childTemplateId>BAB84BFD-9982-4C69-82A7-678024DE24C7</childTemplateId>
              <errorMessage>A sample sub item may only have 1 sub sub item</errorMessage>
            </enforceUniqueTemplateParams>
          </config>
        </handler>
      </event>
    </events>
  </sitecore>
</configuration>

IProcessorBase Interface

This is the interface that the item saved handler will implement. The item saved handler implements that “Add” method.

using System.Xml;

namespace SampleSite.EventHandlers
{
    public interface IProcessorBase
    {
        void Add(XmlNode node);
    }
}

EnforceUniqueTemplateOnItemSavedHandler : IProcessorBase

Look at the “Add” method below. For each of the “elements” directly under the hint=”raw:Add” Sitecore is going to call the “Add” method on our handler and pass the nodes to it. We will then check each node and validate the data before adding it to the “public List Configurations { get; set; }”

In the item:saved event, the save happens. We then extract the saved item from the EventArgs and determine if it violates the configuration. If it does, we delete the item and send the Sheer.Response.

public class EnforceUniqueTemplateOnItemSavedHandler : IProcessorBase
    {
        public List<EnforceUniqueTemplateConfig> Configurations { get; set; }

        public EnforceUniqueTemplateOnItemSavedHandler()
        {
            Configurations = new List<EnforceUniqueTemplateConfig>();
        }

        public void OnItemSaved(object sender, EventArgs args)
        {
            //if custom events are turned off, due to update package installation exit the method
            if (CustomEvents.Instance.Enabled == false)
            {
                return;
            }

            // Extract the item from the event Arguments
            var savedItem = Event.ExtractParameter(args, 0) as Item;

            // Allow only non null items and allow only items from the master database
            if (savedItem == null || savedItem.Database.Name.ToLower() != "master" || !Configurations.Any() || savedItem.Parent == null)
            {
                return;
            }

            var childList = savedItem.Parent.GetChildren(ChildListOptions.None);

            foreach (var config
                in Configurations
                    .Where(conf => conf.ChildTemplateId == savedItem.TemplateID
                                   && childList.InnerChildren.Count(x => x.TemplateID == conf.ChildTemplateId) > 1))
            {
                savedItem.Delete();
                SheerResponse.Alert(config.ErrorMessage);
            }
        }

        public void Add(string config)
        {
            Sitecore.Diagnostics.Log.Warn("The add method with string argument has been called.", "EnforceUniqueTemplateItemSavingHandler");
        }

        public void Add(XmlNode node)
        {
            var parentTemplateId = string.Empty;
            var childTemplateId = string.Empty;
            var errorMessage = string.Empty;

            if (node != null && node.HasChildNodes)
            {
                var parentTemplateIdNode = node["parentTemplateId"];
                var childTemplateIdNode = node["childTemplateId"];
                var errorMessageNode = node["errorMessage"];

                if (parentTemplateIdNode != null)
                {
                    parentTemplateId = parentTemplateIdNode.InnerText;
                }

                if (childTemplateIdNode != null)
                {
                    childTemplateId = childTemplateIdNode.InnerText;
                }

                if (errorMessageNode != null)
                {
                    errorMessage = errorMessageNode.InnerText;
                }

                var config = new EnforceUniqueTemplateConfig(parentTemplateId, childTemplateId, errorMessage);

                if (config.IsValid)
                {
                    Configurations.Add(config);
                }
            }

            var meh = 0;
            Sitecore.Diagnostics.Log.Warn("The add method with xml argument has been called.", "EnforceUniqueTemplateItemSavingHandler");
        }
    }

public class EnforceUniqueTemplateConfig

This class the C# representation of the configuration. The interesting part of this code is the “IsValid” method that checks to make sure the configuration is correct.

public class EnforceUniqueTemplateConfig
    {
        public ID ParentTemplateId { get; set; }

        public ID ChildTemplateId { get; set; }

        public string ErrorMessage { get; set; }

        private ID result;

        public EnforceUniqueTemplateConfig(string parentTemplateId, string childTemplateId, string errorMessage)
        {
            ID _parent = null;
            ID _child = null;

            ID.TryParse(parentTemplateId, out _parent);
            ID.TryParse(childTemplateId, out _child);

            ParentTemplateId = _parent;
            ChildTemplateId = _child;
            ErrorMessage = errorMessage;
        }

        public bool IsValid
        {
            get
            {
                if (ID.TryParse(ParentTemplateId, out result)
                    && ID.TryParse(ChildTemplateId, out result)
                    && !ErrorMessage.IsNullOrWhiteSpace())
                {
                    return true;
                }
                else
                {
                    var errorMessage =
                        string.Format(
                            "Enforce Unique Template Failed to add configuration for: " + Environment.NewLine +
                            "ParentTemplateId: {0}" + Environment.NewLine +
                            "ChildTemplateId: {1}" + Environment.NewLine +
                            "ErrorMessage: {2}", ParentTemplateId, ChildTemplateId, ErrorMessage);

                    Sitecore.Diagnostics.Log.Warn(errorMessage, "EnforceUniqueTemplateItemSavingHandler");
                    return false;
                }
            }
        }
    }

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 →