Recently I came across a situation where I needed to use an NVelocity template in a Rendering Variant. We were creating a custom slider and using the awesome Slick Slider as it gives us a lot of nice options, and it has some good extras for accessibility that the OOTB Carousel component does not have at the time of writing. For the slide content, we still used a Page Content
component and a custom Rendering Variant, but on the slide I wanted to make the Slide Image
field a background image on the main <div>
element. The second requirement was that the design called for 2 types of slide, one with just the background image, and one where we could add a child component into a placeholder. This could be used as a promo/call to action, but needed to be detached from the slide content. This would allow the client to personalize and test the call to action separately from the slide imagery.
Here is an example of the markup I needed to reproduce in the Rendering Variant:
1 | <div class="carousel-slide" style="background-image:url('/-/media/mybackgroundimage.jpg');"> |
The first problem is that currently OOTB you can’t set an image field as a background image on a html tag. Fortunately there are some nice blog posts already out there on how to extend the NVelocity template:
- Michael West has post post on a Custom Rendering Variant Token Tool for SXA
- Chaitanya Marwah hit prettly close with this post on Design Sitecore SXA components using Variant Template
Quick Reminder on how to Extend the Template
Just as a refersher on how to extend the template, first we need a new processor adding to the getVelocityTemplateRenderers
pipeline. Implement IGetTemplateRenderersPipelineProcessor
and add your code to the Process
method:
1 | public class AddTemplateRenderers : IGetTemplateRenderersPipelineProcessor |
And patch it in:
1 | <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/"> |
Next, create the static method to get the url from an image field:
1 | public class FieldTokens |
This now lets us create the Template definition with the following code:
1 | <div class="carousel-slide" style='background-image: url("$fieldTokens.GetImageUrl($item, "SlideImage")")'> |
Creating the Rendering Variant
So the next task is to add the placeholder in the right place. This is where the problem started. There is no way to add a placeholder into a template out of the box. Adding a placeholder requires a valid view context, so trying to add one in the same way that we added the token for the image url would be overly complex.
Michael West came up with a great suggestion, in the template we create the start of the <div>
markup. Like this:
1 | <div class="carousel-slide" style='background-image: url("$fieldTokens.GetImageUrl($item, "SlideImage")")'> |
I would set the Tag
field to empty, so that there is no other markup added. Next we create a section that contains the placeholder items. Finally we can add a Text
item in the variant definition with </div>
in the text to close the earlier div
.
Now all I need to do is to add the placeholder item in between those 2 items and I should be golden!
Huston… We have a problem
Or so I thought… it turns out that even with the Tag
field empty in a VariantTemplate
item still adds a div
surrounding the contents of the Template
field. So the markup looked like this:
1 | <div> |
Which is invalid and although most browsers helpfully try to close the div’s, it didn’t give me what I needed. After a bit of digging, the offending code can be found here in the Sitecore.XA.Foundation.RenderingVariants.Pipelines.RenderVariantField.RenderTemplate
processor. Here is the RenderField
method:
1 | public override void RenderField(RenderVariantFieldArgs args) |
You can see here on line 6, that if the varitantField.Tag
property is null or white space, then a div
tag will be defaulted too! So now I knew where the problem was, I can set about fixing it!
First we need to override the RenderTemplate
processor in the RenderVariantField
pipeline. So create a new processor class and inherit from the existing one. Then override the RenderField
method:
1 | public class RenderTemplate : Sitecore.XA.Foundation.RenderingVariants.Pipelines.RenderVariantField.RenderTemplate |
Now on line 12, we check to see if the variantField.Tag
has been filled in or not. If it has, we pass this through to the base method and let it do its thing. If not, we will take over. We have to get the templateRenderer
and build the parameters
object to be able to render the field. But now we are not creating any surrounding tag, we are just executing the template and adding the resuling html to the args.Result
property.
Now our variant renders exactly as I wanted it to, with a div
containing a background image, the placeholder in the middle and then the closing </div>
tag.
I’m not sure why the default behaviour is to force a div
tag around template variants if the Tag
field is left empty. But hopefully this option will help others who need to be a bit more creative with the rendering variant definitions!
Thanks to Michael West for helping out with the original idea!
–Richard