How to use SXA Content Tokens in a JSS solution


Intro

In a regular MVC SXA solution we usually use Content Tokens for static content that has to be shown in multiple places, for instance, a copyright text. So let's start creating the token we're going to use.

In the Data folder on your site, create a Content Token folder and a Content Token inside of it.

P.S. I'm using Sitecore 10.3, SXA and JSS (React)

Content Token Folder

Path: your-site-path/Data/Content Tokens
Template: /sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token Folder - {29252703-D448-451C-BF1E-65B16174F4B0}

Content Token

Path: /your-site-path/Data/Content Tokens/Copyright
Template: /sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token - {7D659EE9-D487-4D40-8A92-10C6D68844C8}



Once the token is created we can use it in single-line or multi-line text fields like that: $(Copyright).

And in RTE fields the Insert Variable button shows up the Content Token modal, then you can select one of the existing tokens.








The problem

After using the Content Token from any data source item we can see it's only working in RTE fields.


In the MVC world, the processor below is assigned to the renderField pipeline and does the magic, it replaces the token by the correspondent value.

Sitecore.XA.Feature.ContentTokens.Pipelines.RenderField.RenderContentToken

In a JSS world, we don't have such thing, the rendering host will request the data to the Layout Service and a json string containing all the data will be returned, that said, how can we solve this? Is there any way to extend the Layout Service to do the text replacement we need?

Let's think about it!



The Solution

The best option we have is extending the Layout Service by creating a custom field serializer, let's do this!

First, you should create a class that inherits from BaseFieldSerializer and override the WriteValue method, I called this class TokenizedTextFieldSerializer.

public class TokenizedTextFieldSerializer : BaseFieldSerializer
{
    protected Regex RegexTokenInputs { get; } = new Regex("\\$\\(([^$()]+)\\)", RegexOptions.Compiled);
    protected IContext Context { get; } = ServiceLocator.ServiceProvider.GetService<IContext>();
    public TokenizedTextFieldSerializer(IFieldRenderer fieldRenderer)
        : base(fieldRenderer)
    {
    }
    protected override void WriteValue(Field field, JsonTextWriter writer)
    {
        Assert.ArgumentNotNull((object)field, nameof(field));
        Assert.ArgumentNotNull((object)writer, nameof(writer));
    
        string result = field.Value;
        foreach (Match match in this.RegexTokenInputs.Matches(field.Value))
        {
            string tokenValue = this.GetTokenValue(match.Groups[1].Value);
            if (!string.IsNullOrWhiteSpace(tokenValue))
                result = result.Replace(match.Value, tokenValue);
        }
        writer.WriteValue(result);
    }

    private string GetTokenValue(string tokenKey)
    {
        ID keyFieldId = ID.Parse("{02F6FB63-FE92-44E8-AFA2-20747C893502}");
        ID valueFieldId = ID.Parse("{1B9D1028-C20C-407B-9DCD-AFFE86A6F793}");
        if (!string.IsNullOrWhiteSpace(tokenKey))
        {
            ID contentTokenId = ((IEnumerable<ID>)this.Context.Database.DataManager.DataSource.SelectIDs(keyFieldId, tokenKey)).FirstOrDefault<ID>();
            Item tokenItem = this.Context.Database.GetItem(contentTokenId);
            if (tokenItem != null)
            {
                return tokenItem[valueFieldId];
            }
        }
        return string.Empty;
    }
}


Now create a class that inherits from BaseGetFieldSerializer, and override the SetResult method to set an instance of the previous class as the value of args.Result, I called this class GetTokenizedTextFieldSerializer.


public class GetTokenizedTextFieldSerializer : BaseGetFieldSerializer
{
    public GetTokenizedTextFieldSerializer(IFieldRenderer fieldRenderer):base(fieldRenderer)
    {
    }

    protected override void SetResult(GetFieldSerializerPipelineArgs args)
    {
        args.Result = new TokenizedTextFieldSerializer(this.FieldRenderer);
    }
}

Now you should add the GetTokenizedTextFieldSerializer processor to the getFieldSerializer, and patch it before GetDefaultFieldSerializer processor. Do not forget to specify the field types that this field serializer should be applied to, in our case single-line text and multi-line text field types.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
    <pipelines>
      <group groupName="layoutService">
        <pipelines>
            <getFieldSerializer>
                <processor patch:before="*[@type='Sitecore.LayoutService.Serialization.Pipelines.GetFieldSerializer.GetDefaultFieldSerializer, Sitecore.LayoutService']" type="Your.Namespace.GetTokenizedTextFieldSerializer, your.dllName" resolve="true">
                    <FieldTypes hint="list">
                        <fieldType id="1">single-line text</fieldType>
                        <fieldType id="3">multi-line text</fieldType>
                    </FieldTypes>
                </processor>
            </getFieldSerializer>
        </pipelines>
      </group>
    </pipelines>
</sitecore>
</configuration>

And that's it guys, this should fix the problem and now you can create how many content tokens you want and re-use them throughout the whole site.

Please take your conclusions and leave a comment!

References

https://sitecore.stackexchange.com/questions/29893/sxa-content-tokens-in-layout-service

https://doc.sitecore.com/xp/en/developers/hd/211/sitecore-headless-development/customize-the-serialization-of-a-sitecore-field.html

https://www.getfishtank.com/blog/embracing-power-content-tokens-sitecore-sxa

https://doc.sitecore.com/xp/en/users/sxa/103/sitecore-experience-accelerator/create-and-insert-a-content-token.html

https://doc.sitecore.com/xp/en/developers/sxa/103/sitecore-experience-accelerator/content-tokens.html

Comments

Popular posts from this blog

RenderField pipeline - GetImageFieldValue processor returns empty when image comes from Content Hub