Loading component...

Published on April 28, 2022Reading time: 12 min read

How To Highlight Search Results In Sitecore SXA

Loading component...

Related Articles

Sitecore

Intro to SitecoreAI: The unified, AI-first platform

Goodbye XM Cloud. Hello SitecoreAI — the next chapter in Sitecore’s evolution.

December 5, 20251 min read
Blog Card Placeholder
SitecoreAI

Sitecore Workflow Automation with Management API

A practical guide to managing Sitecore workflows programmatically using the Management GraphQL API with production-tested patterns.

November 20, 20251 min read

Loading component...

Digital solutions

Learn how to maximize your investment in Sitecore by working with us.

AI solutions

Dominate your industry. Work with us to digitally transform your business.

Fishtank's proprietary project accelerator. Save time and money. Build what matters.

Utilities

Energy

Education

Healthcare

Manufacturing

Professional services

Financial services

Non-profits and associations

About FishtankThe PlaybookSitecore SnackContact us

Solutions

  • Digital solutions
  • AI solutions
  • TIDAL for XM Cloud

About

  • About Fishtank
  • The Playbook
  • Sitecore Snack
  • Contact us

Insights

Work

Industries

  • Utilities
  • Energy
  • Education
  • Healthcare
  • Manufacturing
  • Professional services
  • Financial services
  • Non-profits and associations

Start typing to search...

Write a script that highlights users' search key in Sitecore SXA's OOTB search components.

Pre-Requisite

  • Basic understanding of OOTB (Out Of The Box) search components, specifically the search results and search box component.
  • Understanding of SXA Search's Search Signature (In short, it's a string that you define in SXA in order to restrict interaction between search components. For example, we can configure a search box to only target one of the search components, but not the others.)
  • Cash JS or JQuery

Overview

As of SXA version 10.0.0, there is no out-of-the-box way of highlighting search keys in the search results. So we must write the logic ourselves. This almost seems like a trivial exercise where we wrap strong tag around the search key in paragraphs. However, we need some additional steps to ensure we don't break SXA's OOTB components

Code Structure

The skeleton of the code will look like this. XA.component.search.xxx refers to the js modules that ship with SXA. We MUST use this because we need to listen to the events that SXA search components fire. In this case, we need to listen to 'results-loaded' event and handle the highlighting of search key in onResultsLoaded method that I will define in a bit. This structure is being used by pretty much every single js module that ship with SXA, so please take a look at them if you re confused.

        
        import $ from "cash-dom";

        XA.component.search.resultsUtil = (function ($, document) {
            var api = {};

            [OMITTED CODE]

            // Listen to Events
            XA.component.search.vent.on("results-loaded", onResultsLoaded);
            
            api.init = function () {};
            return api;

        }(jQuery, document));

        XA.register('searchResultsUtil', XA.component.search.resultsUtil);
        
    

Rest Of The Code

Don't be alarmed by the length. This code is identical to the above except that we just filled in the OMITTED CODE with a bunch of functions. Here is what's basically going on:

  • Locating all search keys from the url hash (q= or [signature-name]_q=). For example, if signature name is abcd, we look for abcd_q. if no signature, look for q
  • Locating all search results
  • Match the search signatures between the search results components and search keys
  • In each search reuslts component, if search key exists, locate them in the html and wrap the target strings in bold (ensure case agonostic)
  • Everytime highlightQueryInSearchResults is called again, clean all the strong tags first before highlighting the new search keys.
Now that you know the general flow, the code itself should read pretty well. Begin reading from XA.component.search.vent.on("results-loaded", onResultsLoaded). I have also added more comments than what's on our production code.

 

        
            import $ from "cash-dom";

            XA.component.search.resultsUtil = (function ($, document) {
                var api = {};

                function getQueryMap(url) {
                    // returns a map from query key to value in sxa's context. if no custom signature, we just need to look for q=
                    // with custom signature, need to look for _q=
                    // example 1 (with custom signature): #sig1_e=0&sig1_q=summary -? {sig1_q: 'summary'}
                    // example 2 (no custom signature): #q=summary -> {q: 'summary'}
                    if (!url.includes('#')) return; // sxa search uses hash. no hash, no param

                    const hash = url.split('#')[1];
                    if (hash == '') return;

                    const validQueryList = hash.split('&').filter(hsh => {
                        return hsh.includes('q=');
                    });

                    const queryMap = {};
                    validQueryList.forEach(queryString => {
                        const queryStringSplit = queryString.split('=');
                        if (queryStringSplit[1] != null && queryStringSplit[1] !== '') {
                            queryMap[queryStringSplit[0]] = queryStringSplit[1].replaceAll('%20', ' ');
                        }
                    });

                    return queryMap;
                }

                function findAllWordsInString(str, word) {
                    // returns locations (indices) of the word in str
                    const strLower = str.toLowerCase();
                    const wordLower = word.toLowerCase();
                    const indices = [];
                    let index = 0;
                    let limiter = 0; // prevent infinite loop
                    while (index < strlower.length="" &&="" index="" !="=" -1="" &&="" limiter="" />< 10000)="" {="" index="strLower.indexOf(wordLower," index);="" if="" (index="" !="=" -1)="" {="" prevents="" infinite="" loop="" caused="" by="" indexof="" returning="" -1,="" therefore="" making="" index="" 0="" indices.push(index)="" index="index" +="" 1;="" }="" limiter++;="" }="" return="" indices;="" }="" function="" boldifystringinhtml(str,="" target)="" {="" const="" indices="findAllWordsInString(str," target)="" for="" (let="" i="indices.length" -="" 1;="" i="" /> -1; i--) {
                        const currentStr = str.substring(0, indices[i])
                            + ''
                            + str.substring(indices[i], indices[i] + target.length)
                            + ''
                            + str.substring(indices[i] + target.length);
                        str = currentStr;
                    }
                    return str;
                }

                function cleanStrongElementsInHTML(str) {
                    // removes all strong tags in a string
                    return str.split('').join('').split('').join('');
                }

                function highlightQueryInSearchResults() {
                    const queryMap = getQueryMap(window.location.href);
                    if (queryMap == null) return;
                    const searchResultsList = $('.component.search-results');
                    for (let i = 0; i < searchresultslist.length;="" i++)="" {="" for="" each="" search="" results="" component="" let="" signature="JSON.parse($(searchResultsList[i]).attr('data-properties')).sig;" signature="signature" signature="" :="" '';="" const="" searchresultbody="$(searchResultsList[i]).find('.search-result" .search-result__body');="" for="" (let="" j="0;" j="" />< searchresultbody.length;="" j++)="" {="" const="" searchresultbodyhtml="$(searchResultBody[j]).html();" if="" (searchresultbodyhtml="==" ''="" ||="" searchresultbodyhtml="=" null)="" continue;="" const="" cleansearchresultbodyhtml="cleanStrongElementsInHTML(searchResultBodyHTML);" if="" (signature="==" '')="" {="" no="" custom="" signature="" if="" ('q'="" in="" querymap)="" {="" $(searchresultbody[j]).html(boldifystringinhtml(cleansearchresultbodyhtml,="" querymap['q']));="" }="" }="" else="" {="" if="" (signature="" +="" '_q'="" in="" querymap)="" {="" with="" custom="" signature,="" need="" to="" identify="" />_q
                                    $(searchResultBody[j]).html(BoldifyStringInHTML(cleanSearchResultBodyHTML, queryMap[signature + '_q']));
                                }
                            }
                        }
                    }
                }

                function onResultsLoaded() {
                    setTimeout(() => { //wait for the DOM to load
                        highlightQueryInSearchResults();
                    })
                }

                // Listen to Events
                XA.component.search.vent.on("results-loaded", onResultsLoaded);
                
                api.init = function () {};
                return api;

            }(jQuery, document));

            XA.register('searchResultsUtil', XA.component.search.resultsUtil);

        
    

Final Results

Highlighted keywords in Sitecore search results

Room For Improvement?

One word: REGEX. It would've simplified the process of locating search key in html. At the time of writing, I was not super comfortable with it.

Thank You

Solutions

  • Digital solutions
  • AI solutions

Brands

  • TIDAL for SitecoreAI
  • Sitecore Snack
  • The Playbook

Fishtank lore

  • Work
  • Insights
  • About us
  • Contact us

Industries

Utilities
Professional services
Energy
Financial services
Healthcare
Education
Non-profits and associations
Manufacturing
Utilities
Professional services
Energy
Financial services
Healthcare
Education
Non-profits and associations
Manufacturing

2025 @All Rights Reserved. Privacy Policy.

FISHTANK