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.
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 🙂 .
protectedoverrideboolContainsToken(SearchStringModel m) => Regex.Match(m.Value, FormattableString.Invariant(FormattableStringFactory.Create("{0}\\|[a-zA-Z ]*", (object)this.TokenPart))).Success;
}
}
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;
}
}
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.
CustomItemsWithTheSameValueInField Demo
ItemsWithIDsInFieldAsCurrentPage – For a complex field types where a single ID or multiple IDs are selected.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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;
protectedoverrideboolContainsToken(SearchStringModel m) => Regex.Match(m.Value, FormattableString.Invariant(FormattableStringFactory.Create("{0}\\|[a-zA-Z ]*", (object)this.TokenPart))).Success;
}
}
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;
}
}
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.
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.