Ayear or so ago ServiceNow didn’t have any real global search capability. You could search or filter within a table list, or you could search the knowledge base like you can now. Everybody wanted global search though. In response to this demand I created a modification to the knowledge search that searched and displayed task records along with knowledge search results. Eventually, development came up with an even better solution that allows us to search globally like we do today.

Now that we can search globally in an instance people are asking us to limit the results again in certain situations. While this modification is not for everyone, it does show a very non-intrusive method for creating separate search pages to limit the search scope on that particular search page to exactly what you want.

Let’s say you have a search group named ‘Tasks’ that you would like to use independently on its own search page. Users accessing your search page should only be able to search items defined within this specific search group. Here’s what my ‘Tasks’ search group looks like…

Global Text Search Group

The basic idea behind this solution is to modify the query that returns the search groups to use. The query should only return results from the search group named ‘Tasks’.

1Create a new UI Page by navigating to ‘System UI -> UI Pages’

UI Page Settings
Name: customtextsearch
Active: true
HTML:

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
    <script language="JavaScript" src="scripts/text_search.jsx?v=${gs.getProperty('glide.builddate')}"/>
    <link href="styles/text_search.cssx?v=${gs.getProperty('glide.builddate')}" type="text/css" rel="stylesheet" />

    <j2:set var="jvar_found_exact_match" value="false"/>
    <!-- are search terms? -->  
    <g2:evaluate jelly="true" var="jvar_search">
        var answer = false;
        if (jelly['sysparm_search'] != undefined) {
          var s = new String(jelly['sysparm_search']);
          s = s.trim();
          if (s.length != 0)
            answer = true;
        }
        answer;  
    </g2:evaluate>

    <j2:if test="$[jvar_search]">
        <!-- save search terms for user and set unescaped_search jvar -->
        <g2:evaluate jelly="true" var="jvar_unescaped_search">
            var term = jelly.sysparm_search;
            if (jelly.sysparm_recent_search == 'true')
                term = Packages.java.net.URLDecoder.decode(jelly.sysparm_search);
               
            var gr = new GlideRecord('ts_query');
            gr.addQuery('user',gs.getUserID());
            gr.addQuery('search_term',term);
            gr.query();
            while (gr.next()) {
                gr.recent = "false";
                gr.update();
            }
            gr.initialize();
            gr.user.setValue(gs.getUserID());
            gr.search_term = term;
            gr.recent = "true";
            gr.insert();
            term;
        </g2:evaluate>
       
        <j:set var="sysparm_view" value="${gs.getProperty('glide.ui.text_search.view')}"/>
        <j2:if test="${gs.getPreference('ts.match','true') == 'true'}">
            <g:inline template="search_match_redirect.xml" />
        </j2:if>
    </j2:if>
    <j2:if test="$[jvar_found_exact_match != 'true']">
        <g:inline template="custom_search_form" />
        <j2:if test="$[jvar_search]">
            <j:set var="jvar_list_only" value="true"/>
            <j2:set var="sysparm_query" value="123TEXTQUERY321=$[jvar_unescaped_search]" />
            <j2:set var="sysparm_force_row_count" value="${gs.getProperty('glide.ui.text_search.rowcount',10)}" />
            <j:set var="jvar_pos_link_msg" value="${gs.getMessage('Navigate to see all results for this table or try a different filter')}" />
            <j:set var="jvar_neg_link_msg" value="${gs.getMessage('Navigate to list for this table to try a different filter')}" />
            <j2:set var="jvar_query_orig" value="$[sysparm_query]"/>
            <j:if test="${sysparm_tsgroups == '' || !sysparm_tsgroups}">
                <!-- got here from ui macro, so get groups to search -->
                <g:evaluate var="sysparm_tsgroups">
                    var ans = '';
                    var tsgroups = new GlideRecord('ts_group');
                                        tsgroups.addQuery('name', 'Tasks');
                    tsgroups.addActiveQuery();
                    var qc = tsgroups.addQuery('group','');
                    qc.addOrCondition('group',getMyGroups());
                    qc.addOrCondition('group.manager',gs.getUserID());
                    tsgroups.orderBy('order');
                    tsgroups.query();
                    while (tsgroups.next()) {
                        if (gs.hasRole(tsgroups.roles)) {
                            if (ans == '')
                                ans = tsgroups.sys_id + '';
                            else
                                ans = ans + ',' + tsgroups.sys_id;
                        }
                    }
                    ans;
                </g:evaluate>
            </j:if>
            <!-- jvar_this_tsparm has the sys_ids of the groups to be searched -->
            <g2:evaluate>
                       if (typeof GlideIRQuerySummary != 'undefined')
                          var summarizer = GlideIRQuerySummary.get();
                       else
                      var summarizer = Packages.com.glide.db.ir.IRQuerySummary.get();
            </g2:evaluate>
            <g:tokenize var="jvar_this_tsparm" delim=",">${sysparm_tsgroups}</g:tokenize>
            <j:forEach var="jvar_ts_groupid" items="${jvar_this_tsparm}">
                <g2:evaluate>
                    var seeGroup = gs.getPreference('ts.group.${jvar_ts_groupid}','true') == 'true';
                </g2:evaluate>
                <j2:if test="$[seeGroup]">
                   <g2:evaluate>
                        summarizer.addGroup('${jvar_ts_groupid}', '$[jvar_query_orig]');
                   </g2:evaluate>
                </j2:if>
            </j:forEach>
            <g:tokenize var="jvar_this_tsparm" delim=",">${sysparm_tsgroups}</g:tokenize>
            <j:forEach var="jvar_ts_groupid" items="${jvar_this_tsparm}">
                <g2:evaluate>
                    var seeGroup = gs.getPreference('ts.group.${jvar_ts_groupid}','true') == 'true';
                </g2:evaluate>
                <j2:if test="$[seeGroup]">
                        <g:inline template="ts_group.xml"/>
                </j2:if>
            </j:forEach>
            <g:inline template="search_summary.xml"/>
        </j2:if>
       
        <script>
            if ("$[jvar_nosearch]" != "true")
                if ("$[jvar_quicknav_text]" == "$[jvar_quicknav_text_orig]")
                    document.getElementById('quicknav2').innerHTML = "${gs.getMessage('no_match_check_spelling')}";
                else {
                    document.getElementById('quicknav2').innerHTML = "$[jvar_quicknav_text]";
                    document.getElementById('additional').innerHTML = "$[jvar_additional_text]";
                }
               
            // stop the spinner
            hideLoading();
           
            // update recent searches dropdowns
            adjustSearch();
            parent.adjustSearch();
       
            function adjustSearch() {
                var ajax = new GlideAjax("TextSearchAjax");
                ajax.addParam("sysparm_name", "recent");
                ajax.getXML(adjustSearchResponse);
            }
   
            function adjustSearchResponse(request) {
                var answer =  request.responseXML.documentElement.getAttribute("answer");
                if (answer != null) {
                    var gcm = new GwtContextMenu('context_searchform');
                    gcm.clear();
                    if (answer.indexOf("xyzzyx") != 0) {
                        var db = answer.split("^");
                        for (var i = 0; i != db.length; i++) {
                            var u = new GlideURL('customtextsearch.do');
                            u.addParam('sysparm_search',escape(db[i]));
                               u.addParam('sysparm_recent_search','true');
                            gcm.addHref(db[i], "executeRecentSearch('"+escape(db[i])+"','"+u.getURL()+"')");
                        }
                    } else gcm.addLabel(answer.substr(6));
                }
            }
        </script>
    </j2:if>
</j:jelly>
-If you want to modify this to use a different search group you just need to modify this line above and change ‘Tasks’ to the name of your search group.
‘tsgroups.addQuery(‘name’, ‘Tasks’);’

-If you want to create more than one custom search page you’ll need to make sure that the above UI Page (along with its corresponding UI Macro) is unique. You can do this by making the following modifications to the UI Page name and HTML above…

  • Find and replace every instance of ‘customtextsearch’ with the name of your new search page.
  • Find and replace the single occurrence of ‘custom_search_form’ with the corresponding name of the UI Macro you create in step 2.

2Create a new UI Macro by navigating to ‘System UI -> UI Macros’

UI Macro Settings
Name: custom_search_form
Active: true
XML:

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
   
 <!-- called from customtextsearch UI page -->
  <g:inline template="javascript_includes.xml" includes_file="scripts/text_search.js" />  
  <g:inline template="search_load_preferences.xml" />

  <div>
    <form action="customtextsearch.do" style="DISPLAY: inline" id="customtextsearch"
          onSubmit="parent.document.getElementById('sysparm_search').value = document.getElementById('sysparm_search').value;
                    showLoading();
                    getCheckedSearchGroups();"
>
        <input type="hidden" id="sysparm_tsgroups" name="sysparm_tsgroups" value="" />

        <div id="searchgroups" style="display:$[jvar_search_groups_display];">
            <table>
                <tr>  
                    <td id="searchgroup_checkboxes">
                        <!-- "select all" checkbox -->
                        <span id="ts_selectall" style="display:inline;"><input type="checkbox" id="ts_all" onclick="tsAllCheckbox()" name="ts_all" /><label for="ts_all"><em>${gs.getMessage('Select all')}</em></label></span>
                        <!-- search group checkboxes -->
                        <g2:evaluate>
                          var tsgroups = new GlideRecord('ts_group');
                                                  tsgroups.addQuery('name', 'Tasks');
                          tsgroups.addActiveQuery();
                          var qc = tsgroups.addQuery('group','');
                          qc.addOrCondition('group',getMyGroups());
                          qc.addOrCondition('group.manager',gs.getUserID());
                          tsgroups.orderBy('order');
                          tsgroups.query();
                        </g2:evaluate>
                        <j2:set var="jvar_nochecked" value="true"/>
                        <j2:set var="jvar_allchecked" value="true"/>
                        <j2:set var="jvar_numvisible" value="0"/>
                        <j2:set var="jvar_edit_group_msg" value="$[gs.getMessage('Select tables in this search group')]"/>
       
                        <j2:while test="$[tsgroups.next()]">
                            <j2:set var="jvar_tsgroup_visible" value="$[gs.hasRole(tsgroups.roles)]"/>
                            <j2:if test="$[jvar_tsgroup_visible]">
                                <g2:evaluate var="jvar_numvisible" expression="$[jvar_numvisible] + 1"/>
                                <j2:set var="jvar_tsgroup" value="$[tsgroups.sys_id]"/>
                                <g2:evaluate var="jvar_tsgroup_checked" expression="gs.getPreference('ts.group.' + '$[jvar_tsgroup]','true')"/>
                                <j2:set var="jvar_desc" value="$[tsgroups.description]"/>
                                <j2:if test="$[jvar_tsgroup_checked]">
                                    <span style="margin-left: 4px;"><input type="checkbox" checked="true" id="ts_group_$[jvar_tsgroup]"
                                     name="ts_group_$[jvar_tsgroup]" onclick="tsGroupCheckbox(this)" title="$[jvar_desc]"/></span>
                                </j2:if>
                                <j2:if test="$[!jvar_tsgroup_checked]">
                                    <span style="margin-left: 4px;"><input type="checkbox" id="ts_group_$[jvar_tsgroup]"
                                     name="ts_group_$[jvar_tsgroup]" onclick="tsGroupCheckbox(this)" title="$[jvar_desc]"/></span>
                                </j2:if>

                                <a onClick="tablePrefs('$[jvar_tsgroup]','$[tsgroups.name]')" class="searchgrouplink" title="$[jvar_edit_group_msg]">$[tsgroups.name]</a>
                                <j2:if test="$[jvar_tsgroup_checked == 'true']">
                                    <j2:set var="jvar_nochecked" value="false"/>
                                </j2:if>
                                <j2:if test="$[jvar_tsgroup_checked == 'false' || jvar_tsgroup_checked == null]">
                                    <j2:set var="jvar_allchecked" value="false"/>
                                </j2:if>
                            </j2:if>
                        </j2:while>
                    </td>
                </tr>
            </table>
        </div>
       
        <div id="searchBoxAndPrefs">
            <table border="0" cellspacing="0" cellpadding="0">
                <tr>
                    <!-- search box -->
                    <td valign="top" nowrap="true" style="background-color:white;">
                        <div style="border: 1px solid #d5d5d5;">
                                <input type="hidden" id="sysparm_tsgroups" name="sysparm_tsgroups" value="" />
                                <g2:evaluate jelly="true">
                                    var term1 = jelly.sysparm_search;
                                    if (jelly.sysparm_recent_search == 'true')
                                        term1 = Packages.java.net.URLDecoder.decode(term1);
                                </g2:evaluate>
                                <input size="45" id="sysparm_search" name="sysparm_search" autocomplete="off" title="${gs.getMessage('Search')}"
                                    value="$[term1]" style="padding-left:3px;border:solid 0px;" onfocus="this.select()"/>
                                <input style="vertical-align:middle;" type="image" class="searchGlass" src="images/search_glass.gifx" title="${gs.getMessage('Search')}" alt="${gs.getMessage('Search')}" width="14" height="18" />
                                <a style="margin-left:4px" onclick="contextShow(event, 'searchform', 200, grabOffsetTop(this) + 20, 0, grabOffsetLeft(this) + 11);event.cancelBubble=true;" title="${gs.getMessage('Recent searches')}">
                                    <img src="images/drop_down.gifx" alt="${gs.getMessage('Recent searches')}" style="vertical-align:middle;" border="0" id="imgText2" width="9" height="18" /></a>
                        </div>
                    </td>
                    <td width="99%"></td>
                    <j2:if test="$[gs.hasRole('text_search_admin')]">
                        <td valign="top" align="right" nowrap="true"><a class="ts_adminlink" href="ts_group_list.do" title="${gs.getMessage('Navigate to list of search groups for administration')}">${gs.getMessage('Edit Search Groups')}</a></td>
                    </j2:if>                  
                </tr>
                <tr>
                    <j2:set var="jvar_quicknav_text_orig" value="$[gs.getMessage('Found: ')]"/>
                    <j2:set var="jvar_quicknav_title" value="$[gs.getMessage('Scroll page to results for this table')]"/>
                    <j2:set var="jvar_quicknav_text" value="$[jvar_quicknav_text_orig]"/>
                    <td colspan="2" width="99%" id="quicknav2"></td>              
                    <g:inline template="search_tips_and_preferences.xml" />
                </tr>
            </table>
        </div>
       
        <!-- focus in search box -->
        <script>
            var l = gel('sysparm_search'); if (l) l.focus();
        </script>
    </form>
    <j2:if test="$[jvar_allchecked]">
        <script>document.getElementById("ts_all").checked = true;</script>
    </j2:if>
    <j2:if test="$[jvar_nochecked &amp;&amp; jvar_numvisible != 0]">
        <j2:set var="jvar_nosearch" value="true"/>
        <script>
            document.getElementById('quicknav2').innerHTML ="<em>${gs.getMessage('No search groups selected - please check at least one')}</em>";
            var e = document.getElementById("searchgroup_checkboxes");
            e.style.backgroundColor = "#ffffcc";
            e.style.borderWidth = "1px";
            e.style.borderStyle = "inset";
            document.getElementById("searchgroups").style.display = "block";
        </script>
    </j2:if>
    <j2:if test="$[jvar_numvisible == '1']">
        <script>document.getElementById("ts_selectall").style.display = "none";</script>
    </j2:if>
    <j2:if test="$[jvar_numvisible == '0']">
        <j2:set var="jvar_nosearch" value="true"/>
        <script>document.getElementById("ts_selectall").style.display = "none";
                document.getElementById("quicknav2").innerHTML = "<em>${gs.getMessage('No search groups available to you, contact your admin for details')}</em>";
        </script>
    </j2:if>
  </div>
</j:jelly>
-If you want to modify this to use a different search group you just need to modify this line above and change ‘Tasks’ to the name of your search group.
‘tsgroups.addQuery(‘name’, ‘Tasks’);’

-If you want to create more than one custom search page you’ll need to make sure that the above UI Macro (along with its corresponding UI Page) is unique. You can do this by making the following modifications to the UI Macro name and XML above…

  • Find and replace every instance of ‘customtextsearch’ with the name of your custom UI Page in step 1.
  • Find and replace every instance of ‘custom_search_form’ in your UI Macro above with the name of your new UI Macro.

Now you can access your new search page ‘customtextsearch’ by navigating to ‘https://yourinstancename/customtextsearch.do’ (or creating a module or gauge that points to that location). Searching from this form will yield search results only from the ‘Tasks’ search group defined previously.

Global Text Search Results