Popular search engines make use of autocomplete. It helps predict what users are looking for. If a user is unsure of what they are trying to find, autocomplete can help users find exactly what they are trying to find. This blog post will show you how to provide this predictive feature for your search box in a Sitecore solution.
Step 1 – Register the Route in the Initialize Pipeline
Patch into the Sitecore config a definition for the route registration process you are going to create like so:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <initialize> <processor type="SampleProject.Feature.Search.Pipelines.Initialize.RegisterAutocompleteRoute, SampleProject.Feature.Search" patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" resolve="true" /> </initialize> </pipelines> </sitecore> </configuration>
Note that we are patching this process to be performed before the Sitecore InitializeRoutes
process.
What does this RegisterAutocompleteRoute
processor code look like? It can look something like this:
using System.Web.Mvc; using System.Web.Routing; using Sitecore.Pipelines; namespace SampleProject.Feature.Search.Pipelines.Initialize { public class RegisterSearchSuggestionsRoute { public virtual void Process(PipelineArgs args) { Register(RouteTable.Routes); } private static void Register(RouteCollection routes) { routes.MapRoute("GetAutocompleteSuggestions", "sampleprojectapi/search/{action}", new {controller = "Autocomplete" }); } } }
The code above adds my custom route the RouteTable
. Once my C# code is all set up, I will be calling this route in the front-end in our Javascript. Warning: When you register your route and specify the controller to find the method to execute, be sure to drop the Controller
suffix. In the code above, I am requesting to call a method in the AutocompleteController
. I do not need to worry about adding the Controller
suffix because .NET works its magic behind the scenes to find the controller with the Autocomplete
name.
Step 2 – Create an HttpGet Method in the Controller
Now that we have properly registered our route, we need to make the method that will be called when the route is requested. In Step 1 above, I specified that the method must live in the AutocompleteController
, so let’s make that controller now.
namespace SampleProject.Feature.Search.Controllers { public class AutocompleteController { private const int NumSuggestionsToProvide = 4; private const int NumSuggestionsToCheck = 12; [HttpGet] public JsonResult GetAutoCompleteSuggestions(string searchTerms) { // whatever logic you need to bring back suggestions // my example is below return !string.IsNullOrWhiteSpace(searchTerms) ? Json(GetSuggestions(searchTerms), JsonRequestBehavior.AllowGet) : Json(new EmptyResult(), JsonRequestBehavior.AllowGet); } private IEnumerable<string> GetSuggestions(string searchTerms) { using (var context = GetSearchContext()) { var baseQuery = PredicateBuilder.True<SearchResultItem>(); var searchTermsTokens = !string.IsNullOrEmpty(searchTerms) ? searchTerms.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries) : new string[] { "" }; foreach (var searchTermsToken in searchTermsTokens) { if (!string.IsNullOrEmpty(searchTermsToken)) { // build a predicate that requires item names to match user-input var containsOrEqualsSearchTermQuery = PredicateBuilder.True<SearchResultItem>(); containsOrEqualsSearchTermQuery = containsOrEqualsSearchTermQuery .Or(i => i.Name.Contains(searchTermsToken)); containsOrEqualsSearchTermQuery = containsOrEqualsSearchTermQuery .Or(i => i.Name.Equals(searchTermsToken)); baseQuery = baseQuery.And(containsOrEqualsSearchTermQuery); } } return context.GetQueryable<SearchResultItem>().Where(baseQuery) .Select(s => s.Name).Take(NumSuggestionsToCheck) .ToList().Distinct().Take(NumSuggestionsToProvide); } } } }
Your HttpGet
method can contain whatever logic you need to get the proper autocomplete suggestions you want. In the example above, I am going to the search index to compare user-input to the names of Sitecore items and returning at most 4 suggestions. I provide this example for a number of reasons. First, I wanted to show that I am returning a JSON response that the front-end can easily digest and retrieve whatever information is needed. Second, the user may type in multiple search terms into the search box. You will need to perform a .Split()
on the user-input to get each individual term from the search box. You will then need to build the predicate to make sure the autocomplete includes every term the user inputted. Third, in my example, there may be multiple Sitecore items with the same name in the index. That’s why I needed to take a subset of the results (12 in the example above) and make a .Distinct()
call to make sure I have 4 unique suggestions to provide. Lastly, take note of the parameter in the GetAutocompleteSuggestions()
method signature. When the front-end calls this method, the query string parameter must match the parameter found in this signature: searchTerms
.
Step 3 – Calling the Route in the Front-End
The final step to wiring up autocomplete is to make the call to the route in the front-end on the keyup
event. On the keyup
event, you will construct an XMLHttpRequest
to make a call to the route we created above and retrieve the JSON from that call (Example: https://<home-page-url>/sampleprojectapi/search/getautocompletesuggestions?searchTerms=<user-input>
). You can then display the values found in the JSON somewhere in your markup to show the predictive text. There are two recommendations I would like to call out here. First, I would recommend not making any calls to the route until the user types in at least 3 characters. Providing suggestions for a shorter length may prove useless since a single vowel keystroke would match on a lot of items. Second, set a slight delay on making a call to the route in case the user is typing quickly.
Conclusion
Setting up autocomplete for a search box with Sitecore is as simple as that! Your users will greatly appreciate this predictive feature to help them find what they are trying to find. Have any questions about this blog post? Hit up the comment section, and I will get back to you.