Feb 2019 Update - I have moved the Handlebar Code into it’s own project and its available as a stand alone module without needing to worry about Sitecore Foundation. You can find the code and installable Sitecore Packages on github here.
In my last post I discussed forking my framework to work with SXA. Today I want to talk about how I extended SXA to support a new type of Rendering Field Variant. No, not a component that supports rendering variants, but a field type that can be added to any component that supports rendering variants.
While SXA actually supported nVelocity templates and supports extending those templates, I didn't see a ton of documentation on how to use that feature and I'm not sure how it compares to my handlebar implementation. But when I saw how it worked as a Rendering Variant Field Type, I thought I could probably plug in my handlebar implementation to do something similar. The result works quite well.
There wasn't a ton of documentation on how to implement this, so I spent a decent amount of time reflecting on SXA code and looking through good old "Showconfig.aspx". In the end to add your own rendering field type you need to define a template, custom insert options, a few classes and configure a few custom pipeline processors.
Step 1: Creating a Variant Field Template
First you need to create a custom template that will allow variant authors to configure your field rule. This template should inherit from the "Base Variant Field" (/sitecore/templates/Foundation/Experience Accelerator/Variants/Base Variant Field), "_Rendering Variant Base" (/sitecore/templates/Foundation/Experience Accelerator/Rendering Variants/Base/_Rendering Variants Base), and "_Data Attributes" (/sitecore/templates/Foundation/Experience Accelerator/Rendering Variants/Base/_Data Attributes)
Step 2: Enable your field to be inserted
I got very comfortable with Insert Option Rules from using DEF, so I'm using the same approach to enable your new field type to be inserted in a variant definition. Note that I use rules to enable it to be put in the root of the Variant Definition as well as under other variant fields to enable nested fields to work properly.
Step 3: Write Some Code
To get this to work, we'll need to actually create three different classes. First you'll need to create a class to represent the type of the template you just created. It needs to inherit RenderingVariantFieldBase and should include any properties that you would find helpful when writing your Render Processor.
public class HandlebarVariantTemplate : RenderingVariantFieldBase
{
public HandlebarVariantTemplate(Item variantItem) : base(variantItem)
{
}
public string Template
{
get;
set;
}
public Item TemplateItem { get; set; }
}
In order to create an instance of this class, you'll need to create a ParseVariantFieldProcessor. This is responsible for defining the supported TemplateId that matches to your custom template and mapping the instance of that template to your FieldClass.
public class ParseHandlebarTemplate : ParseVariantFieldProcessor
{
public ParseHandlebarTemplate()
{
}
public override ID SupportedTemplateId
{
get
{
return new ID(Templates.HandlebarVariantTemplate);
}
}
public override void TranslateField(ParseVariantFieldArgs args)
{
args.TranslatedField = new HandlebarVariantTemplate(args.VariantItem)
{
ItemName = args.VariantItem.Name,
Template = args.VariantItem[new ID(Fields.Template)],
TemplateItem = args.VariantItem
};
}
}
Last, you'll need to create a RenderRenderingVariantFieldProcessor to actually output markup for the variant. You'll need to set the appropriate renderermode and define the SupportedType to match the field type class you created.
public class RenderHandlebarTemplate : RenderRenderingVariantFieldProcessor
{
public override RendererMode RendererMode
{
get
{
return RendererMode.Html;
}
}
public override Type SupportedType
{
get
{
return typeof(HandlebarVariantTemplate);
}
}
public RenderHandlebarTemplate()
{
}
public override void RenderField(RenderVariantFieldArgs args)
{
var variantField = args.VariantField as HandlebarVariantTemplate;
if (variantField != null)
{
HtmlGenericControl htmlGenericControl = new HtmlGenericControl((string.IsNullOrWhiteSpace(variantField.Tag) ? "div" : variantField.Tag));
this.AddClass(htmlGenericControl, variantField.CssClass);
this.AddWrapperDataAttributes(variantField, args, htmlGenericControl);
var content = HandlebarManager.GetTemplatedContent(variantField.TemplateItem, args.Item);
htmlGenericControl.InnerHtml = content.ToHtmlString();
args.ResultControl = htmlGenericControl;
args.Result = this.RenderControl(args.ResultControl);
}
}
}
Step 4: Configure Your Processors
To get your parser and renderer to actually fire, you'll need to patch the SXA pipelines.
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<parseVariantFields>
<processor type="SF.Feature.Handlebars.Variants.ParseHandlebarTemplate, SF.Feature.Handlebars" resolve="true"/>
</parseVariantFields>
<renderVariantField>
<processor type="SF.Feature.Handlebars.Variants.RenderHandlebarTemplate, SF.Feature.Handlebars" resolve="true"/>
</renderVariantField>
</pipelines>
</configuration>
The result is a pretty easy and quick way to define markup for a component without having to create nodes like a typical variant. Fully Handlebar Syntax is supported including any helper functions that you define (even placeholders).
Here's a short video of the feature in action.
As mentioned all code is in the SXA branch in github.