Step 2: Create a Yext Search Results Widget for your Service Portal

Overview

Most help sites have a dedicated search results page that all search bars across the site will redirect to when a user makes a search. Generally this lives on a URL like https://yourdomain.com/search or https://search.yourdomain.com/.

In this step, we will walk through how to embed your Yext Search experience into a Service Portal search page. If you would like to host the Search Results page using Yext, a CMS, or different system, you can certainly do so.

Prepare your Frontend Repository for the ServiceNow Integration

  1. In the Yext Platform, navigate to your repository via Pages > All Sites > Your Experience.

  2. Navigate to the *Code Editor**. Then, click on the pen icon on the desired branch to view and edit the repository code.

  3. In the files on the left-hand side, navigate to config > global_config.json and make sure the initializeManually property is set to true.

    initialize manually

  4. Now, navigate to layouts > headincludes.hbs and add the following script:

      <script>
     function bindEvent(element, eventName, eventHandler) {
       if (element.addEventListener) {
           element.addEventListener(eventName, eventHandler, false);
       } else if (element.attachEvent) {
           element.attachEvent('on' + eventName, eventHandler);
       }
     }
     // Listen to messages from parent window and handle accordingly
     bindEvent(window, 'message', function (e) {
       try {
           var obj = JSON.parse(e.data);
           if (obj.action === 'setQuery') {
               ANSWERS.core.setQuery(obj.value)
           }
           if (obj.action === 'passConfig') {
               const snPageParams = obj.value.querySource;
               const parsableParams = new URLSearchParams(snPageParams);
               const snPageId = parsableParams.get('id');
               const querySource = snPageId === 'search_yext' ? 'HELP_SITE' : snPageId === 'sc_cat_item' ? 'CASE_DEFLECTION' : 'DEFAULT';
    
               AnswersExperience.runtimeConfig.set("querySource", querySource);
               AnswersExperience.runtimeConfig.set("visitor", obj.value.visitor);
               AnswersExperience.init({});
           }
       } catch (e) {}
          
     });
     </script>

    NOTE: In the script shown above, the querySource is being set conditionally depending on the ID of the particular Service Portal page where the experience is embedded.

    The above code assumes the ID of the search results page is search_yext and the ID of the incident / case form page is sc_cat_item. If you utilize different IDs for these pages within your Service Portal Configuration, you’ll need to update the above script accordingly.

  5. Commit and publish your changes. Now your frontend search experience repository is ready to be integrated with ServiceNow Service Portal!

Create a Custom Search Results Widget

  1. Navigate to Service Portal > Widgets. Click New to create a new widget.

  2. Fill in the widget name as Yext Search Results and the widget ID as yextsearchresults.

  3. Replace all of the code in the Body HTML template box with the following code:

      <div id="answers-container"></div>
  4. Replace all of the code in the Client controller box with the following code. Note: make sure you fill in the placeholder with your search experience production URL on line 3 of the code snippet below. Make sure to remove any trailing slashes from the URL.

     api.controller=function() {
     var c = this;
     var url = REPLACE_ME_PRODUCTION_URL;
     var userId = c.data.user;
     var sourceUrlParams = window.top.location.search;
    
     generateIFrame(url);
        
     var targetNode = document.getElementById('answers-frame');
     var config = {attributes: true};
     var callback = function() {
        initializeExperience(userId,sourceUrlParams);
     }
     var observer = new MutationObserver(callback);
     observer.observe(targetNode, config);
         
     };
    
     function generateIFrame(domain) {
      var containerEl = document.querySelector('#answers-container');
     if (containerEl == null) {
     containerEl = document.createElement('div');
        containerEl.setAttribute('id', 'answers-container');
     }
     var iframe = document.createElement('iframe');
     var pathToIndex = containerEl.dataset.path;
     iframe.allow = 'geolocation';
    
     domain = domain || '';
     var queryParam = queryParam || 'query';
     var urlParam = urlParam || 'verticalUrl';
    
     var calcFrameSrc = function() {
     var paramString = window.location.search;
     paramString = paramString.substr(1, paramString.length);
    
     // Decode ASCII forward slash to avod repeat encodings on page refreshes
     paramString = paramString.replace("%2F", "/");
    
     // Parse the params out of the URL
     var params = paramString.split('&'),
                 verticalUrl;
     var referrerPageUrl = document.referrer.split('?')[0].split('#')[0];
    
     if (pathToIndex) {
      verticalUrl = pathToIndex;
     }
    
     // Don't include the verticalUrl or raw referrerPageUrl in the frame src
     var new_params = params.filter(function(param) {
       return (param.split('=')[0] !== 'verticalUrl') &&
        (param.split('=')[0] !== 'referrerPageUrl');
     });
    
     for (var i = 0; i < params.length; i ++) {
      var kv = params[i].split('=');
      if (kv[0] === 'verticalUrl') {
        verticalUrl = kv[1];
      }
    
      if (kv[0] === 'referrerPageUrl') {
        referrerPageUrl = kv[1];
      }
     }
    
     new_params.push('referrerPageUrl=' + referrerPageUrl);
    
     // Build the Iframe URL
     var iframeUrl = domain;
     if (verticalUrl) {
      iframeUrl += '/' + verticalUrl;
     }
    
     iframeUrl += '?' + new_params.join('&');
     return iframeUrl;
     };
    
     iframe.src = calcFrameSrc();
     iframe.frameBorder = 0;
    
     // For dynamic iFrame sizing
     iframe.style.width = '1px';
     iframe.style.minWidth = '100%';
     iframe.id = 'answers-frame';
    
     containerEl.appendChild(iframe);
        
     // For dynamic iFrame resizing
     window.iFrameResize({
     checkOrigin: false,
     onMessage: function(messageData) {
      var message = JSON.parse(messageData.message);
      if (message.action === "paginate") {
        var iframeOffsetTop = iframe.offsetTop;
        document.documentElement.scrollTop = iframeOffsetTop;
        document.body.scrollTop = iframeOffsetTop; // For Safari
        return;
      }
      var params = message.params;
      var pageTitle = message.pageTitle;
      if (pageTitle) {
                iframe.title = pageTitle;
            }
      iframe.iFrameResizer.resize();
      // var currLocation = window.location.href.split('?')[0];
      // var newLocation = currLocation + '?' + params;
      // if (window.location.href !== newLocation) {
      //  history.replaceState({query: params}, window.document.title, newLocation);
      // }
     }
     }, '#answers-frame');
     }
        
     function initializeExperience(user,src) {
        var visitorObj = {
            id: user,
            idMethod: "sn_user_id"
        }
        var configObject = {
            querySource: src,
            visitor: visitorObj
        }
            var msg = JSON.stringify({
                action: "passConfig",
                value: configObject
            });
            var frameEl = document.getElementById("answers-frame");
            frameEl.contentWindow.postMessage(msg, "*");
        };
  5. Replace the code in the Server script with the following code.

      ​​(function() {
    data.user = gs.getUserID();
      })();
  6. Click Submit.

  7. Navigate back to the widget and scroll to the bottom of the page to the Dependencies table. Click New to create a new dependency

  8. Fill in the content for the widget dependency:

    • Fill in the Name as iFrame Resizer JS.
    • Check off Include on page load.
    • Click Submit.
  9. Scroll back down to the Dependencies table and click on the iFrame Resizer JS dependency you just created.

  10. On the JS Includes tab, click New.

Congratulations, you’ve now created your search results widget! Next, we’ll walk through creating a Yext Search Results Page with your new widget.

Replace Your Current Service Portal Search Page With a Yext-Powered Search Results Page

  1. Navigate to Service Portal > Pages.

  2. Click New to create a new page. Title the page Yext Search Results and change the ID of the page to search_yext.

  3. Click Submit to save the page. Then, navigate to your new search results page by clicking on it.

  4. Open the Page Designer. Scroll to the bottom of the page. In the Related Links section and click Open in Designer.

  5. Set up the page for the search results container. The Service Portal Designer application will open in a new browser tab.

    • We recommend adding a one-column container (12) and dropping in both the breadcrumbs widget to help users navigate as well as the Yext Search Results widget you just created.

    • If you are using an existing page, delete widgets and containers on the page or reorganize them to make room for the search results container. To delete a widget or container, select it (the dotted lines will become a solid line) and click the trash can icon in the upper right corner.

  6. Click Preview to see your new page; it should look something like the screenshot below.

    search results page

Now you’ve got your search results page ready to go! The Relative URL associated with this page will be your redirectUrl when you initialize the Search experience in your search bar widget as part of the next step. Note that if you choose to not host the Search Results page in ServiceNow, simply make sure you use that alternate URL instead.