Sitecore SXA Scriban

Create a function for the Scriban template in SXA 9.3.0

Dominic Sinclair-Moore
Coffee required.

Introduction

SXA Scriban functions are used to execute different logic on either Sitecore items, strings or fields. The most common functions that will be used are sc_execute, sc_raw, sc_field, sc_follow, sc_link and sc_query. These functions are pretty self explanatory in regards to what they do but each function will take a different input. It is also worth mentioning that with the pipe separator "|" functions can be executed at any point in Scriban. I find that using the sc_field function is not that useful as the shorthand to this is to append the field name ({{ i_item.FieldName }}) as an item extension. On the flip side, the sc_follow function is the most useful function to hand as it allows you to return an item that is selected from a field and stores links to items. If the field contains multiple items, only the first item is returned. If the field is empty the function also returns a null value. There are plenty of functions given to us by the SXA team at Sitecore but even with all of these functions it is tough to do certain queries, for example getting the URL of a link field item. Of course we could use the sc_link function to render our link field but if we want just the URL it is not as simple. We have to create a linkField function to allow us to get the URL of the link field.

Creation

First of all, we need to install the Sitecore.XA.Foundation.Scriban and Scriban NuGet packages to the project we are enabling this functionality in. The Sitecore Scriban package contains the GenerateScribanContext pipeline which we need to hook into to register our function with Scriban within Sitecore. The Scriban package contains the Import function we need to include our function in the Scriban template.

Now lets create a class called AddLinkFieldFunction and inherit and implement the IGenerateScribanContextProcessor interface from the Sitecore.XA.Foundation.Scriban package we've previously installed.

using Scriban.Runtime;
using Sitecore;
using Sitecore.XA.Foundation.Scriban.Pipelines.GenerateScribanContext;

namespace Sitecore.Foundation.Variants.Pipelines.GenerateScribanContext
{
    public class AddLinkFieldFunction : IGenerateScribanContextProcessor
    {
        public void Process(GenerateScribanContextPipelineArgs args)
        {
        }
    }
}

Sitecore's implementation of Scriban takes advantage of what's known as a GlobalScriptObject which is available as part of the GenerateScribanContextPipelineArgs arguments. This GlobalScriptObject is where we need to Import our new function and the Delegate we define for the function. The Import function only allows for the use of a delegate when defining a member name. In our case we want to name our function as "sc_linkField" to follow the Sitecore syntax for functions. We therefore need to create a delegate for our function import, a method to execute the logic, reference our delegate in the Process method and to Import the function name "sc_linkField" with our delegate.

using Scriban.Runtime;
using Sitecore.XA.Foundation.Scriban.Pipelines.GenerateScribanContext;
using Sitecore.Data.Items;
using System;

namespace Sitecore.Foundation.Variants.Pipelines.GenerateScribanContext
{
    public class AddLinkFieldFunction : IGenerateScribanContextProcessor
    {
        public void Process(GenerateScribanContextPipelineArgs args)
        {
            var linkField = new LinkFieldIt(LinkField);
            args.GlobalScriptObject.Import("sc_linkField", (Delegate)linkField);
        }

        public string LinkField(Item item, string field, bool useAbsoluteUrl = false){
            return "Logical URL";
        }

        private delegate string LinkFieldIt(Item item, string field, bool useAbsoluteUrl = false);
    }
}

At this point we have a function that would return "Logical URL" if we called it within a Scriban template eg. {{ sc_linkField i_item "Link Field" }}

Our custom function is expecting an Item, a string and an optional boolean value which we have done so above. We now need to implement a helper class to fetch our LinkField URL.

Create a new static class called ItemExtensions and create the below helper methods to allow for the different scenarios of link rendering. You may find that an existing solution may already have an extension class or methods similar to this.

using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Links;
using Sitecore.Links.UrlBuilders;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sitecore.Foundation.Extensions.ItemExtensions
{
    public static class ItemExtensions 
    {
        public static string GetAbsoluteUrl(this Item item)
        {
            var urlOptions = LinkManager.GetDefaultUrlBuilderOptions();

            urlOptions.AlwaysIncludeServerUrl = true;

            return item?.GetItemUrl(urlOptions);
        }

        public static string GetShortUrl(this Item item)
        {
            ItemUrlBuilderOptions urlOptions = LinkManager.GetDefaultUrlBuilderOptions();

            urlOptions.AlwaysIncludeServerUrl = false;

            return item != null ? LinkManager.GetItemUrl(item, urlOptions) : string.Empty;
        }

        public static string GetLinkFieldUrl(this Item item, string fieldName, bool useAbsoluteUrl = false)
        {
            var linkField = item.GetLinkField(fieldName);

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

            if (linkField.IsInternal && linkField.TargetItem != null)
            {
                return useAbsoluteUrl ? linkField.TargetItem.GetAbsoluteUrl() : linkField.TargetItem.GetShortUrl();
            }

            return linkField.Url;
        }

        public static string GetItemUrl(this Item item, ItemUrlBuilderOptions urlOptions = null)
        {
            if (urlOptions == null)
            {
                urlOptions = LinkManager.GetDefaultUrlBuilderOptions();
            }

            return item != null ? LinkManager.GetItemUrl(item, urlOptions) : string.Empty;
        }

    }
}

We can now include a reference to our ItemExtensions class and call the GetLinkFieldUrl method with our parameters parsed in from the Scriban function. Be cautious when including your reference as Sitecore.Data.Items.ItemExtensions can cause errors, the best way round this is to define ItemExtensions to be our referenced class.

using Scriban.Runtime;
using Sitecore.XA.Foundation.Scriban.Pipelines.GenerateScribanContext;
using Sitecore.Data.Items;
using ItemExtensions = Sitecore.Foundation.Extensions.ItemExtensions.ItemExtensions;
using System;

namespace Sitecore.Foundation.Variants.Pipelines.GenerateScribanContext
{
    public class AddLinkFieldFunction : IGenerateScribanContextProcessor
    {
        public void Process(GenerateScribanContextPipelineArgs args)
        {
            var linkField = new LinkFieldIt(LinkField);
            args.GlobalScriptObject.Import("sc_linkField", (Delegate)linkField);
        }

        public string LinkField(Item item, string field, bool useAbsoluteUrl = false){
            return ItemExtensions.GetLinkFieldUrl(item, field, useAbsoluteUrl);
        }

        private delegate string LinkFieldIt(Item item, string field, bool useAbsoluteUrl = false);
    }
}

At this point we have our new link field function ready to be included within the Sitecore pipeline for Scriban. To do this we need to create a patch file which adds a processor of our new type to the generateScribanContext node after the initialisation of the Scriban context. Our patch file looks as follows.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
    <sitecore>
        <pipelines>
          <generateScribanContext>
              <processor type="Sitecore.Foundation.Variants.Pipelines.GenerateScribanContext.AddLinkFieldFunction, Sitecore.Foundation.Variants" 
                          patch:after="processor[@type='Sitecore.XA.Foundation.Scriban.Pipelines.GenerateScribanContext.InitializeScribanContext, Sitecore.XA.Foundation.Scriban']" />
          </generateScribanContext>
        </pipelines>
    </sitecore>
</configuration>

There we have it, we now have our AddLinkFieldFunction processor within Sitecore and we can now take advantage of it within a Scriban template. To test it we can create a simple Scriban template using our function with and without the optional link parameter. As this is a function, we can use it at any time within Scriban to fetch the link field value of items not necessarily on the context item level.

{{ 
sc_linkField i_item "Link Field"
sc_linkField i_item "Link Field" true
sc_follow i_item "Promo" | sc_linkField i_item "Promo Link" false
}}
Cookie Policy© 2021 — DOTNET Ltd