Sitecore Datasource Folder Insert Rule

November 28, 2017

Blog | Development | Sitecore Datasource Folder Insert Rule
Sitecore Datasource Folder Insert Rule

Removing a step when it comes to developing is always exciting especially when it’s a cycle that is repeated constantly. Updating insert options to a datasource folder’s __Standard Values is one of those steps. When John Rappel’s blog post about Helix was published, I started thinking that I really liked the idea of splitting up datasource items and repository items. It gave me the opportunity to write a simple query that would get all basic insert options that should be added to a datasource folder. I was allowed to run with this idea on a project using a field that I’ve seen little documentation about, Insert Rules.

It’s worth noting that this is different than some of the other blog posts that will also show up when searching for insert rules. Generally these posts involve using the rules engine in order to manipulate insert options based on some condition. It is possible that something similar can be done with the rules engine to mimic the outcome of what I’m doing but the method of doing so would be different.

A datasource folder is a concept we use when creating Sitecore projects that take on a modular approach. These folders will generally be nested beneath every page. The goal of these folders is to contain all the datasource items for their corresponding page. This means to support adding renderings to a generic page, it’s datasource folder should have insert options for any item that can be a datasource for a rendering. The relevant part is that whenever a new rendering is made the generic content folder’s standard values item should have a new insert option added assuming the rendering requires a datasource.

At Geekhive this doesn’t just save the step of adding insert options. It’s a little more complicated due to a tool we frequently use. In TDS, the serialization used on this project, item ids for the insert option field are saved to a text file as a single line of ids which makes merging changes for this particular field a pain. Using an insert rule this issue was avoided completely on this project.

Instance of the Insert Rules field:

Instance of the Insert Rules field:

Definition of the Insert Rules field:

Definition of the Insert Rules field:

The insert rule field is a Tree List Ex field that allows the selection of items that are created with the template type insert rule. It uses /sitecore/system/Settings/Insert Rules as its datasource. Following this I created my own insert rule item and tied it to a custom class.

insert rule item tied to a custom class

The custom class inherits from Sitecore.Data.Masters.InsertRule. This class has an abstract method called Expand. Expand takes a list of items called masters and a Sitecore Item as parameters. The masters parameter contains all the items that have been added to insert options. Adding or removing elements from this list causes changes to the insert options when a content editor tries to insert under the item with this insert rule applied to it. The parameter item is the currently selected item. In my custom code, I run a query which gets items under /sitecore/templates/User Defined/Datasources/Implementations for any item that inherits from Sitecore’s system template item (/sitecore/templates/System/Templates/Template) and append that to the masters list. I used Sitecore’s Content Search API to run this query. I created a base class that did some standard checks and a query rule which inherits the base rule. The query rule contains the code for searching based on some abstract properties. Lastly, I have classes that use the query rule class that essentially fill out the abstract properties.

Base class:

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

namespace InsertRules.Sitecore.PathAnalyzer.Client.InsertRules
{
public abstract class BaseInsertRule : InsertRule
{
protected BaseInsertRule(object obj)
{
}

public override void Expand(List masters, Item item)
{
if (masters == null || item == null)
return;

var insertItems = GetInsertItems(masters, item);

if (insertItems == null)
return;

var enumerable = insertItems as Item[] ?? insertItems.ToArray();
if (!enumerable.Any())
return;

if (OverrideMasters)
{
masters.Clear();
masters.AddRange(enumerable);
return;
}

foreach (var obj in enumerable)
masters.Add(obj);
}

public abstract IEnumerable GetInsertItems(List masters, Item item);
public virtual bool OverrideMasters => false;
}
}

Base query class:

using System.Collections.Generic;
using System.Linq;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Linq;
using Sitecore.ContentSearch.SearchTypes;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Mvc.Extensions;

namespace InsertRules.Sitecore.PathAnalyzer.Client.InsertRules
{
public abstract class QueryInsertRule : BaseInsertRule
{
protected QueryInsertRule(object obj) : base(obj)
{
}
public abstract string RootTemplatePath();

public abstract ID TemplateId();

public override IEnumerable GetInsertItems(List masters, Item item)
{
var path = RootTemplatePath();

if (path.IsWhiteSpaceOrNull())
{
return null;
}

var db = item.Database;

var rootItem = db.GetItem(path);

if (rootItem == null)
{
return null;
}

var masterIds = masters.Select(masterItem => masterItem.ID).ToArray();

using (var context = ContentSearchManager.GetIndex((SitecoreIndexableItem)rootItem).CreateSearchContext())
{
var queryable = context.GetQueryable();

queryable = queryable.Where(it => it.Path.StartsWith(rootItem.Paths.FullPath)
&& it.TemplateId == TemplateId());

if (masterIds.Any())
{
queryable = queryable.Where(it => !masterIds.Contains(it.ItemId));
}

var results = queryable.GetResults();
return results.Select(it => it.Document.GetItem()).OrderBy(it => it.DisplayName).ToArray();
}
}
}
}

Datasource folder insert rule:

using InsertRules.Sitecore.PathAnalyzer.Client.InsertRules;
using Sitecore.Data;

namespace MyProject.Common.Sitecore.PathAnalyzer.Client.InsertRules
{
public class ContentFolderInsertRule : QueryInsertRule
{
public ContentFolderInsertRule(object obj) : base(obj)
{
}
public override string RootTemplatePath()
{
return MyProject.Constants.ItemPaths.DatasourceTemplatePath;
}

public override ID TemplateId()
{
return MyProject.Constants.ItemIds.StandardTemplateId;
}
}
}

Potential Improvements:

  • The base class might be improved by using the ref keyword while passing the masters parameter. This way classes that are derived from it will have more control since changes they make to that collection will be reflected rather than the base class needing to add them.
  • This should take into account branch templates. Sometimes it makes sense to have a rendering use a branch template when there’s a distinct item structure that should be added to it.
  • Adding some kind of caching for the query. As long as the query is going to be the same for every content item based on template this could be an in memory dictionary where the key is the template id.
  • Writing a Sitecore powershell script, a dev only save event or command that sets insert options on the content folder is potentially better because this way there wouldn’t be the need to run something like a query when a content clicks the item they want to insert a new item into making it more efficient.
  • Extending the template for an insert rule (/sitecore/templates/System/Branches/Insert Rule) and giving it fields to support a query would make it far easier to use this. As it is a new class needs to be made extending the base class. I didn’t do this because I don’t like changing default Sitecore values and this would involve me changing the source field of the Insert Rules field.

The following shows the insert rule in action. All I need to do for generic datasource folders is assign the insert rule to its standard values item and it will stay up to date with all datasource items as an insert option.

an example of the insert rule item in action

If you are going the route of having generic pages that allow any module then following a solution like this can ease the development process, especially if using tools that have issues like those mentioned in the article.

Update:

After a suggestion saying this was available out of the box with the rules engine I started digging around. Unfortunately, dynamically adding insert options based on a query is not available out of the box however it’s not too bad to set up using a custom action. Here is a gist to the custom action.

To use the custom action you will need to create a new action item under the folder for actions about insert options as follows:

Then create a rule that uses this action:

The bonus of using the rules engine is that parameters for the query (or the query itself if that kind of flexibility is needed) will be manageable in Sitecore – meaning no new code will be needed when a new root or template ID is desired. Give it a shot!

Tino DeRico, Developer, GeekHive

Tino DeRico

Developer
Tags
  • Innovation
  • Sitecore
  • Tutorial
  • Web Development
Sitecore Survival Guide Volume 1

If you found this post helpful, there are plenty more where it came from! You'll find the first 10 here.

Download the first 10 posts in our Sitecore how-to series by Developer Tim Leverett!

Recent Work

Check out what else we've been working on