Rules Engine and Rendering Variants

The other day I was watching a presentation and learned something new. Out of the box, you can control insert options using the rules engine. Along that line of thinking, I decided to see if I could use the rules engine to customize what rendering variants show up on a component.

With some customizations, I was able to use the rules engine to add/remove available rendering variants. However, there is one caveat: Sitecore caches the rendering variants using a cache key made up of the current page ID and the rendering item (in /sitecore/layouts) ID. If you change the rules that affect rendering variants, you’ll need to either recycle the app pool or do something that forces the rendering variant cache to clear.

Sitecore Configuration

Adding Tags

We’re going to start off by adding a taxonomy tag by right-clicking /sitecore/system/Settings/Rules/Definitions/Tags and inserting a new Tag called “Rendering Variants”. This is important because it allows us to specify what rules will show up in the context.

Creating Rule Context

Then, we’re going to go to the content editor of Sitecore and find the rules root item, /sitecore/system/Settings/Rules. From there, we’re going to right-click and insert a rules context folder called Rendering Variants. You should end up with something that looks like this:

Rendering Variants Rule Context Folder

Rendering Variants Rule Context Folder

Now we need to add some tags to the Default tag. For now, I’ve added “Item Hierarchy,” “Item Attributes,” and “Rendering Variants”:

Adding the Tags

Adding the Tags

Creating the Actions

Next, we’re going to go to the /sitecore/system/Settings/Rules/Definitions/Elements folder and insert a new Element Folder called “Rendering Variants”:

Inserting a new Element Folder

Inserting a new Element Folder

In this newly created element folder, we want to insert two new actions:

  1. Add Rendering Variant
  2. Remove Rendering Variant

For the “Add Rendering Variant”, we’re going to set the Text to be:

add [renderingvariantid,Tree,root=/sitecore/content,specific] variant

For the “Remove Rendering Variant”, we’re going to set the Text to be:

remove [renderingvariantid,Tree,root=/sitecore/content,specific] variant

To break down the parts inside the bracket:

  1. variable name in code where we’ll store the value
  2. type of dialog to display
  3. options for the dialog
  4. text to show to the user

Lastly, we’re going to expand the Tags folder inside the Rendering Variants Element Folder and add “Rendering Variants” to the Default tag:

Adding "Rendering Variants" to the Default tag

Adding “Rendering Variants” to the Default tag

Code

Firstly, we need to setup our RuleContext which will store information needed for our rule:

public class FilterVariantsRuleContext : RuleContext
{
    public IList<Item> Variants { get; set; }
}

Next, I want to write a base class for the rule action. When a rule condition is met, the rules engine executes rule actions. I created a BaseRenderingVariantOption that goes like this:

public abstract class BaseRenderingVariantOption<T> : RuleAction<T> where T : FilterVariantsRuleContext
{
    private ID _renderingVariantId;
 
    public ID RenderingVariantId
    {
        get
        {
            ID renderingVariantId = _renderingVariantId;
            return renderingVariantId != (ID)null ? renderingVariantId : ID.Null;
        }
        set
        {
            Assert.ArgumentNotNull(value, nameof(value));
            _renderingVariantId = value;
        }
    }
}

That “RenderingVariantId” is going to be populated from the “renderingvariantid” parameter from the brackets of the rule actions from the earlier section.

Next, we setup our AddRenderingVariant and RemoveRenderingVariant options:

public class AddRenderingVariant<T> : BaseRenderingVariantOption<T> where T : FilterVariantsRuleContext
{
    public override void Apply(T ruleContext)
    {
        Assert.ArgumentNotNull(ruleContext, nameof(ruleContext));
        Item contextItem = ruleContext.Item;
        Item variant = contextItem?.Database.GetItem(RenderingVariantId);
        if (variant == null)
            return;
 
        if(!ruleContext.Variants.Contains(variant, new ItemIdComparer()))
            ruleContext.Variants.Add(variant);
    }
}
 
public class RemoveRenderingVariant<T> : BaseRenderingVariantOption<T> where T : FilterVariantsRuleContext
{
    public override void Apply(T ruleContext)
    {
        Assert.ArgumentNotNull(ruleContext, nameof(ruleContext));
        Item contextItem = ruleContext.Item;
        ID variantId = contextItem?.Database.GetItem(RenderingVariantId)?.ID;
        if (variantId == (ID)null)
            return;
        Item variantToRemove = ruleContext.Variants.FirstOrDefault(v => v.ID == variantId);
        if (variantToRemove != null)
            ruleContext.Variants.Remove(variantToRemove);
    }
}

That “RenderingVariantId” is going to be populated from the “renderingvariantid” parameter from the brackets of the rule actions from the earlier section.

Next, we setup our AddRenderingVariant and RemoveRenderingVariant options:

public class AddRenderingVariant<T> : BaseRenderingVariantOption<T> where T : FilterVariantsRuleContext
{
    public override void Apply(T ruleContext)
    {
        Assert.ArgumentNotNull(ruleContext, nameof(ruleContext));
        Item contextItem = ruleContext.Item;
        Item variant = contextItem?.Database.GetItem(RenderingVariantId);
        if (variant == null)
            return;
 
        if(!ruleContext.Variants.Contains(variant, new ItemIdComparer()))
            ruleContext.Variants.Add(variant);
    }
}
 
public class RemoveRenderingVariant<T> : BaseRenderingVariantOption<T> where T : FilterVariantsRuleContext
{
    public override void Apply(T ruleContext)
    {
        Assert.ArgumentNotNull(ruleContext, nameof(ruleContext));
        Item contextItem = ruleContext.Item;
        ID variantId = contextItem?.Database.GetItem(RenderingVariantId)?.ID;
        if (variantId == (ID)null)
            return;
        Item variantToRemove = ruleContext.Variants.FirstOrDefault(v => v.ID == variantId);
        if (variantToRemove != null)
            ruleContext.Variants.Remove(variantToRemove);
    }
}

Finishing touches

Back into Sitecore, we’re going to go back to our Actions in /sitecore/system/Settings/Rules/Definitions/Elements/Rendering Variants and fill in the Type field.

The Type field of Add Rendering Variant should be:

Example.AddRenderingVariant, Example

The Type field of Remove Rendering Variant should be:

Example.RemoveRenderingVariant, Example 

Adding a Rule

To add a rule, we’re going to go back down to the Rendering Variants Rule Context Folder we created in the beginning, right-click on “Rules” and add a new rule. After the rule is created, there will be a field called “Rule” where you can edit the rule. If everything’s setup correctly, it should look like this:

Adding a Rule

Adding a Rule

NOTE: In case you skipped my introduction, Sitecore does caching around Rendering Variants so I’m not sure how practical this all is.