Monday, September 28, 2020

Custom SXA media query token

SXA query tokens

The resolveTokens pipeline is used to resolve tokens that can be used in a Sitecore SXA site in queries at several places: a query rendering variant, a template field source..  

In the multisite project that I'm working on -which resulted in a bunch of posts already- we wanted to have the source of an Image field to point towards a folder inside the media library that exists for each site.

One of the ootb tokens that can be used is $siteMedia. The problem with this token is that it will be resolved into the virtual media root within the site - which means that adding extra folders (e.g. $siteMedia/pdf) in the query does not work - the resulting path does not exist because the resolved part is a virtual media folder.

We can create a custom resolver for our own token however. This has been described by Maciej Gontarz in his blog post - there are some particular bits into our custom token however so I wanted to share anyway.

Custom SXA media root token

The idea is simple: we want to have the actual root of the media items for the site (inside the Media Library) and not the virtual one (inside the site). This way we can add a subfolder to it and still have a valid path. 

Note: this can really help your editors to get into the correct folder immediately, but it's also a bit dangerous as you will get an error when opening your item in the content editor when the resulting folder does not exist.  

To resolve our token we need a resolver - a processor to add into the pipeline:
using Sitecore.XA.Foundation.Multisite;
using Sitecore.XA.Foundation.Multisite.Extensions;
using Sitecore.XA.Foundation.TokenResolution.Pipelines.ResolveTokens;

public class ResolveTokens : ResolveTokensProcessor
{
  private const string Token = "$mediaSiteRoot";
  private readonly IMultisiteContext multisiteContext;

  public ResolveTokens(IMultisiteContext multisiteContext)
  {
      this.multisiteContext = multisiteContext;
  }

  public override void Process(ResolveTokensArgs args)
  {
      args.Query = ReplaceTokenWithItemPath(args.Query, Token, () => GetMediaRoot(args.ContextItem), args.EscapeSpaces);
  }

  private Item GetMediaRoot(Item contextItem)
  {
    var mediaRoot = multisiteContext.GetSiteMediaItem(contextItem);
    if (mediaRoot == null)
    {
      return null;
    }

    var root = mediaRoot.GetVirtualChildren();
    return root.Any() ? root.First() : mediaRoot;
  }
}
We defined our token as "$mediaSiteRoot" but you can call it (almost) anything you want - just try to avoid picking something that starts with an already existing token.

We use the ReplaceTokenWithItemPath function (comes with the ResolveTokensProcessor) as this will handle everything for us if we pass the correct item. To find our root item we start by fetching the media root item for the site using the MultisiteContext. If this is found, we can use the extension method GetVirtualChildren to get all virtual children that are defined on that item. 

SXA will define 2 children on the virtual media root - by default it's the first one we are looking for.

Now we need to add this resolver to the resolveTokens pipeline:
<sitecore>
  <pipelines>
    <resolveTokens>
      <processor type="Feature.Multisite.Pipelines.ResolveTokens, Feature.Multisite" resolve="true" 
             patch:before="processor[@type='Sitecore.XA.Foundation.TokenResolution.Pipelines.ResolveTokens.EscapeQueryTokens, Sitecore.XA.Foundation.TokenResolution']" />
    </resolveTokens>
  </pipelines>
</sitecore>
As you can see we patch this in before the EscapeQueryTokens. This means our token will be after the standard ones -which is ok- but it has to be before the escape one because otherwise the query won't work as it will be escaped (with #) already before we do our magic.

Using the custom token

We are now ready to use the token. In the source of our Image field we can now put things like 
query:$mediaSiteRoot/Content/Hero


Conclusion

We can now guide our editors to the correct folders inside the media library. But be aware - as mentioned before those folders do need to exist in all sites where you use the template. In our case we start from a master site that gets cloned to create new sites. This also means that the (media) folders that are created in that master site will get created in all new sites, which is great of course as we can make sure that certain folders exist this way. SXA can be really great sometimes 😃

3 comments:

  1. Awesome! Thanks for sharing the knowledge!

    ReplyDelete
  2. Used your tip and it worked as a charm in a multilanguage site.
    However, when I added a second language aside from EN, the pipeline is never called.






    public class ResolveTagFilterEnabledToken : ResolveTokensProcessor
    {
    public override void Process(ResolveTokensArgs args)
    {
    Start(args); //Never hits the Process method
    }

    I have no idea what this could be.

    ReplyDelete
    Replies
    1. For assistance and/or questions it's best to reach out on Sitecore StackExchange (https://sitecore.stackexchange.com/) - that platform is way better suited for this.

      Delete