Experience Sitecore ! | Script rendering variant field in SXA - why would one need it?

Experience Sitecore !

More than 200 articles about the best DXP by Martin Miles

Script rendering variant field in SXA - why would one need it?

Note! The code used in this post can be cloned from GitHib repository: SXA.Foundation.Variants

Yet another rendering variant field came to my to-do list for implementation - Script rendering variant field. Why would I need one at all? 

I came across two uses cases where implementing this field type made my job done and that's just recently. Some developers have (reasonable) biases against having JavaScript code inline instead of referencing JS-file at the bottom of a page, and I do understand their point - Google may penalise your site for overusing inline JS. So use it responsibly, don't overuse. Keeping in mind that given field comes up as a part of a component dynamically added to a page - there isn't a big choice on how to extend running website with some additional client-side functionality. I am presenting both cases below, let's take a look at them. Would you know the better way of achieving these goals - please let me know via Slack or Twitter. Also, the code of Script Variant Field is at the very bottom of the page.


Use case 1: implementing in-page navigation panel

As usual, I got a precise requirement from my strict front-end team to implement such a piece of code as a component. It has a UL-tag that will keep a link list to other components of this same page (prefixed with #) created client-side dynamically, and a script that does the actual job. When rendering the in-page navigation component by the backend, we're now aware of other components and their attributes, so the walk-around was handling page loaded event and identifying all the components that have IDs set and class section-with-anchor.

<div class="component content col-12 content-section">
    <div class="component-content">
        <div class="anchor-panel">
            <ul class="anchor-panel__list"></ul>
            <script defer>
                document.addEventListener('DOMContentLoaded', function(){
                    if (!$('.section-with-anchor').length || !$('.section-with-anchor').length) return;
                    $('.section-with-anchor').each(function(index, el) {
                        var anchor = '#' + $(el).attr('id');
                        var text = $(el).attr('data-text');
                        var $anchorList = $('.anchor-panel__list');
                        var $anchorItem = $('<li class="anchor-panel__item"></li>');
                        var $anchorLink = $('<a href=""></a>')
                        $anchorItem.append($anchorLink.text(text).attr('href', anchor));
                        $anchorList.append($anchorItem);
                    });
                });
            </script>
        </div>
    </div>
</div>

Quite obviously, my rendering variant will contain 2 fields: a UL-tag section field for in-page navigation that takes the links dynamically and script variant field, containing the client-side logic. You can test this script in action by this link. That is how it looks in Content Editor:


Since I have 5 other sections qualifying script requirements - they all have been identified by the script and added to in-page navigation panel. Here's how the result looks like on a styled page for me:


Once again, I decided to implement a new script variant reference field because the component is subject to some seldom minor JavaScript changes, but should be configurable. Also, at given use case it can be dropped only once into a page, so there's no problem with multiple instances of the same script for me, but using it you might need to check if that applies for your scenarios. But even with that in mind, I'd probably not bothered creating yet another new rendering variant field, if not few more potential usages I have in backlog.


Use case 2: accessing client side URL hash-key parameter and presenting it on a page

Recently I implemented a URL query string parameter variant field, where one can set any parameter and the field present its URL-decoded value on a page in any given tag and style. A colleague of mine who develops search results page with SXA asked if that's doable to extract a hash-key URL parameter and show it on a page along with other content, however that a fully client-side parameter that never got posted to the server.

I decided to give a try and managed to confirm with a small proof of concept. I quickly wrote this script (so please be forgiving for it being just a quick PoC and me not being the proper FED).

window.addEventListener("hashchange", function () {
    var h1 = document.getElementsByClassName("updatedHashValue");
    if (h1.length > 0) {
        h1[0].innerHTML = getHashValue("param");
    }
    function getHashValue(parameter) {
        var hashValues = window.location.hash.substr(1);
        var result = hashValues.split('&').reduce(function (result, item) {
                var parts = item.split('=');
                result[parts[0]] = parts[1];
                return result;
        }, {});
        return result[parameter];
    };
});

That's how it looks implemented as the part of rendering variant. It creates an H1 element to store the value, extracted out of hash parameters stored at URL bar and updated without any postback to the server, and of source script variant field:


Testing. After adding a component to a page, saving and selecting the above rendering variant, I got the page reloaded as anticipated with no visual changes. Then I open a console from browser dev.tools and enter:

window.location.hash = "param=Successful!"

From dev.tools that was easy to confirm that there was no postback done to the server, however, browser navigation bar predictably changed, appending new hash parameters pair: 

And guess what? H1 element immediately got that value displayed. Love that magic!


The code is very simple, similar to other variant fields I've blogged previously. Create the model and implement property for a field:

public class VariantScript : VariantField
{
    public string Script { get; set; }
}

Reference ID of that field from a template and ID of a template itself:

public static class Constants
{
    public static class RenderingVariants
    {
        public static class Templates
        {
            public static ID Script = new ID("...");
        }
        public static class Fields
        {
            public static class Script
            {
                public static ID ScriptField { get; } = new ID("...");
            }
        }
    }
}

Here's a parser

public class ParseScript : ParseField
{
    public override ID SupportedTemplateId => Constants.RenderingVariants.Templates.Script;

    public override void TranslateField(ParseVariantFieldArgs args)
    {
        ParseVariantFieldArgs variantFieldArgs = args;

        variantFieldArgs.TranslatedField = new VariantScript
        {
            Script = args.VariantItem[Constants.RenderingVariants.Fields.Script.ScriptField]
        };
    }
}

and a renderer:

public class RenderScript : RenderVariantField
{
    public override Type SupportedType => typeof(VariantScript);

    public override RendererMode RendererMode => RendererMode.Html;

    public override void RenderField(RenderVariantFieldArgs args)
    {
        var variantField = args.VariantField as VariantScript;
        if (variantField != null)
        {
            args.ResultControl = RenderScriptField(variantField, args);
            args.Result = RenderControl(args.ResultControl);
        }
    }

    protected virtual Control RenderScriptField(VariantScript variantScript, RenderVariantFieldArgs args)
    {
        if (!string.IsNullOrWhiteSpace(variantScript.Script))
        {
            var tag = new HtmlGenericControl("script") { InnerHtml = variantScript.Script };
            tag.Attributes.Add("defer", String.Empty);
            return tag;
        }

        return new LiteralControl();
    }
}

New Script Rendering Variant field, that enhances your tooling for implementing more modern-looking websites. And as usual - please use it responsibly!

Comments are closed