Skip to main content

Sitecore

Custom SXA token for search scope query to support all the filter operations

Marketo Lead Scoring

Prerequisites:

Challenge:

We can provide multiple search filters in the search scope query for an SXA search results component. We can use the predefined SXA search tokens in the filter.

If a single search filter starts with then the Operation applied to the filter is
+ must (AND operation)
not (NOT operation)
Nothing is set or default should (OR operation)

The challenge with predefined SXA tokens like ItemsWithTheSameValueInField is that it always considers the operation “must”. As a result, even if we toggle the filter i.e. change the operation as shown below it does not affect the search result.

1 Itemswiththesamevalueinfielddemo

ItemsWithTheSameValueInField Demo

Same applies to SXA tokens like TaggedTheSameAsCurrentPage and TaggedWithAtLeastOneTagFromCurrentPage. Basically, the implementation of these two tokens is identical except for the filter type i.e. operation has different values. “must” operation is set in the case of TaggedTheSameAsCurrentPage, and the “should” operation is set in the case of TaggedWithAtLeastOneTagFromCurrentPage. The operation is hardcoded in the backend. This is awesome in scenarios where we do not need to toggle the filter operation and use these specific SXA tokens.

These 3 tokens compare the current page field value with the search results items’ field value to filter the result.

Solution:

We can implement new Search tokens inspired by the predefined token implementation with some modifications. We can supply the operation set by the content editor user in the search scope as in the following implementations.

Code commented with “setting the operation given in the scope” is the key, and also do check other comments for reference 🙂 . 

CustomItemsWithTheSameValueInField – For a simple field type or field types where only a single ID is saved.

using Sitecore.ContentSearch.Utilities;
using Sitecore.Data.Fields;
using Sitecore.XA.Foundation.Search.Attributes;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using System;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
    public class CustomItemsWithTheSameValueInField : ResolveSearchQueryTokensProcessor
    {
        protected string TokenPart { get; } = nameof(CustomItemsWithTheSameValueInField);

        protected string Operation { get; set; } = "should";

        [SxaTokenKey]
        protected override string TokenKey => FormattableString.Invariant(FormattableStringFactory.Create("{0}|FieldName", (object)this.TokenPart));

        public override void Process(ResolveSearchQueryTokensEventArgs args)
        {
            if (args.ContextItem == null)
                return;
            for (int index = 0; index < args.Models.Count; ++index)
            {
                SearchStringModel model = args.Models[index]; //SearchStringModel holds a specific filter detail given in the scope
                if (model.Type.Equals("sxa") && this.ContainsToken(model))
                {
                    string str = model.Value.Replace(this.TokenPart, string.Empty).TrimStart('|');
                    Field field = args.ContextItem.Fields[str];
                    if (field != null)
                    {
                        this.Operation = model.Operation; //setting the operation given in the scope
                        args.Models.Insert(index, this.BuildModel(str, field.Value)); //pass the field value for filter
                        args.Models.Remove(model);
                    }
                }
            }
        }

        protected virtual SearchStringModel BuildModel(
          string replace,
          string fieldValue)
        {
            return new SearchStringModel("custom", FormattableString.Invariant(FormattableStringFactory.Create("{0}|{1}", (object)replace.ToLowerInvariant(), (object)fieldValue)))
            {
                Operation = this.Operation
            };
        }

        protected override bool ContainsToken(SearchStringModel m) => Regex.Match(m.Value, FormattableString.Invariant(FormattableStringFactory.Create("{0}\\|[a-zA-Z ]*", (object)this.TokenPart))).Success;
    }
}

Following is a short demo where fields Team and Buddy are of a field type Single line text and Droplink respectively.

2 Customitemswiththesamevalueinfielddemo

CustomItemsWithTheSameValueInField Demo

ItemsWithIDsInFieldAsCurrentPage – For a complex field types where a single ID or multiple IDs are selected.

using Sitecore.ContentSearch.Utilities;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.XA.Foundation.Search.Attributes;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using Sitecore.XA.Foundation.SitecoreExtensions.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
    public class ItemsWithIDsInFieldAsCurrentPage : ResolveSearchQueryTokensProcessor
    {
        protected string TokenPart => nameof(ItemsWithIDsInFieldAsCurrentPage);

        protected string Operation { get; set; } = "should";

        [SxaTokenKey]
        protected override string TokenKey => FormattableString.Invariant(FormattableStringFactory.Create("{0}|FieldName", (object)this.TokenPart));

        public override void Process(ResolveSearchQueryTokensEventArgs args)
        {
            if (args.ContextItem == null)
                return;
            for (int index = 0; index < args.Models.Count; ++index)
            {
                SearchStringModel model = args.Models[index];
                if (model.Type.Is("sxa") && this.ContainsToken(model))
                {
                    string str = model.Value.Replace(this.TokenPart, string.Empty).TrimStart('|');
                    MultilistField field = (MultilistField)args.ContextItem.Fields[str];
                    if (field != null)
                    {
                        Item[] items = field.GetItems();
                        foreach (Item obj in ((IEnumerable<Item>)items).Reverse<Item>()) //loop through all the selected IDs
                        {
                            this.Operation = model.Operation; //setting the operation given in the scope
                            args.Models.Insert(index, this.BuildModel(str, Convert.ToString(obj.ID))); //pass the selected item ID for filter
                        }
                        if (items.Length >= 2)
                            index += items.Length - 1;
                        args.Models.Remove(model);
                    }
                }
            }
        }

        protected virtual SearchStringModel BuildModel(
          string replace,
          string fieldValue)
        {
            return new SearchStringModel("custom", FormattableString.Invariant(FormattableStringFactory.Create("{0}|{1}", (object)replace.ToLowerInvariant(), (object)fieldValue)))
            {
                Operation = this.Operation
            };
        }

        protected override bool ContainsToken(SearchStringModel m) => Regex.Match(m.Value, FormattableString.Invariant(FormattableStringFactory.Create("{0}\\|[a-zA-Z ]*", (object)this.TokenPart))).Success;
    }
}

Following is a short demo where fields Services and Skills are of a field type Multilist.

3 Itemswithidsinfieldascurrentpage

ItemsWithIDsInFieldAsCurrentPage Demo

Patch the above classes as below.

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <resolveSearchQueryTokens>
        <processor type="CustomSXA.Foundation.Search.SearchQueryToken.CustomItemsWithTheSameValueInField, CustomSXA.Foundation.Search" resolve="true" />
        <processor type="CustomSXA.Foundation.Search.SearchQueryToken.ItemsWithIDsInFieldAsCurrentPage, CustomSXA.Foundation.Search" resolve="true" />
      </resolveSearchQueryTokens>
    </pipelines>
  </sitecore>
</configuration>

Refer to the list of the NuGet packages from here.

Check out the repository for the above implementation in the CustomSXA.Foundation.Search project.

Hope this helps.

Happy Sitecore Search Query Scoping 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Sandeepkumar Gupta

Sandeepkumar Gupta is a Lead Technical Consultant at Perficient. He enjoys research and development work in Sitecore. He has contributed to Sitecore projects based on SXA, ASP.NET MVC, and Web Forms. He has also contributed to Sitecore upgrade projects.

More from this Author

Categories
Follow Us