Blog Post

Automatically add ‘Search As You Type’ to every SharePoint page using Infuser

Clavin Fernandes
Illustration: Automatically add ‘Search As You Type’ to every SharePoint page using Infuser

It’s only been a day since we launched our free Infuser tool  and we already have an excellent application for it; Add Search as you Type support to SharePoint’s standard search box.

As many of you will be aware, Jan Tielens created a proof of concept some time ago to demonstrate how you could add this functionality to a custom web part. Very innovative, but unfortunately it has a couple of problems, most notably:

  • The script has to be added manually to every page on the site, which is very laborious and not always possible.

  • It doesn’t integrate with the standard MOSS search box, leaving the end user with two search boxes to choose from.

  • If the user doesn’t have access to the root site of the portal then search doesn’t work.

  • Resizing the window with the search results causes undesired effects.

  • The search button is present, but doesn’t work.

  • It is not clear how to make the search results window disappear.

  • It relies on the MOSS Search web service and doesn’t work with WSS.

Although Jan’s script works without modification in Infuser, we asked Jan’s permission and spent a couple of hours to address the points listed above with the exception of WSS compatibility, feel free to add this yourself. The full source code is listed at the end of this article.

SearchAsYouType

Follow the steps outlined below to add Search as you Type to your site collection:

  1. Download and install Muhimbi’s SharePoint Infuser on one of your Web Front End Servers.

  2. Ensure you have Designer privileges, more specifically the Add and Customize Pages right.

  3. From the Site Actions / Site Settings screen, select Infuse custom content in the Look and Feel column.

  4. Download the code and paste it into the Infuser window. If copying from the window below and you are using IE then you may want to paste it in WordPad first , otherwise all line breaks are stripped out.

  5. Click the Save button and navigate to any page that contains a search box and start typing.

<style type="text/css">
  /* Hide the search scope fields as we don't take their input */
  .ms-sbscopes {
    display: none;
  } /* MOSS */
  #idSearchScope {
    display: none;
  } /* WSS */

  .quickSearchResultDivUnselected {
    background: white;
    border: 1px solid white;
    margin-left: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .quickSearchResultDivSelected {
    background: #eeeeee;
    border: 1px solid Gray;
    margin-left: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
  }
</style>

<!-- Load the JQuery Libraries that ship with Infuser -->
<script
  type="text/javascript"
  src="/_layouts/Muhimbi.Infuser/JQuery/jquery-1.3.2.min.js"
></script>

<script type="text/javascript">
  // QuickSearch v0.2
  // Created by Jan Tielens, https://weblogs.asp.net/jan
  // Modified to integrate with standard Seachbox by www.muhimbi.com
  // This sample code is provided on an “as is” basis and without warranty of any kind.

  // *** Customizable parameters ***
  var quickSearchConfig = {
    delay: 500, // time to wait before executing the query (in ms)
    minCharacters: 3, // minimum nr of characters to enter before search
    scope: 'All Sites', // search scope to use 'All Sites'
    numberOfResults: 15, // number of results to show
    resultsAnimation: 200, // animation time (in ms) of the search results
    resultAnimation: 0, // animation time (in ms) of individual result (when selected)
  };

  var quickSearchTimer;
  var quickSearchSelectedDivIndex = -1;
  var searchBox = null;

  // ** Hook up the various events
  jQuery.event.add(window, 'resize', resizeWindow);
  jQuery.event.add(document, 'click', hideResultsDiv);

  $(document).ready(function () {
    // ** The searchbox uses a dynamic ID so select it by class
    searchBox = $('.ms-sbplain');

    // Muhimbi, insert the results box after te search area
    searchBox.after(
      '<div id="quickSearchResults" style="display: none; z-index:1000"></div>'
    );

    searchBox.keyup(function (event) {
      var previousSelected = quickSearchSelectedDivIndex;

      // catch some keys
      switch (event.keyCode) {
        case 13: // enter
          var selectedDiv = $(
            '#quickSearchResults>div:eq(' + quickSearchSelectedDivIndex + ') a'
          );
          if (selectedDiv.length == 1)
            window.location = selectedDiv.attr('href');
          break;
        case 38: // key up
          quickSearchSelectedDivIndex--;
          break;
        case 40: // key down
          quickSearchSelectedDivIndex++;
          break;
      }

      // check bounds
      if (quickSearchSelectedDivIndex != previousSelected) {
        if (quickSearchSelectedDivIndex < 0) quickSearchSelectedDivIndex = 0;
        if (
          quickSearchSelectedDivIndex >=
          $('#quickSearchResults>div').length - 1
        )
          quickSearchSelectedDivIndex = $('#quickSearchResults>div').length - 2;
      }

      // select new div, unselect the previous selected
      if (quickSearchSelectedDivIndex > -1) {
        if (quickSearchSelectedDivIndex != previousSelected) {
          unSelectDiv(
            $('#quickSearchResults>div:eq(' + previousSelected + ')')
          );
          selectDiv(
            $('#quickSearchResults>div:eq(' + quickSearchSelectedDivIndex + ')')
          );
        }
      }

      // if the query is different from the previous one, search again
      if ($(searchBox).data('query') != $(searchBox).val()) {
        if (quickSearchTimer != null)
          // cancel the delayed event
          clearTimeout(quickSearchTimer);
        quickSearchTimer = setTimeout(function () {
          // delay the searching
          $('#quickSearchResults').fadeOut(200, initSearch);
        }, quickSearchConfig.delay);
      }
    });
  });

  function showResultsDiv(text) {
    var div = $('#quickSearchResults');
    resizeWindow();
    div.append(text).slideDown(quickSearchConfig.resultsAnimation);
  }

  function hideResultsDiv() {
    var div = $('#quickSearchResults');
    div.slideUp(quickSearchConfig.resultsAnimation);
    div.empty();
  }

  function resizeWindow() {
    var div = $('#quickSearchResults');
    var searchParent = $(searchBox).parent();

    var divCss = {
      left: searchParent.offset().left,
      padding: 0,
      position: 'absolute',
      top: searchParent.offset().top + searchParent.height() + 1,
      border: '1px solid #7f9db9',
      width: searchParent.width(),
      background: 'white',
      'max-width': searchParent.width(),
    };

    div.css(divCss);
  }

  function unSelectDiv(div) {
    // first stop all animations still in progress
    $('#quickSearchResults>div>div').stop(true, true);

    div
      .removeClass('quickSearchResultDivSelected')
      .addClass('quickSearchResultDivUnselected');
    $('#details', div).hide();
  }

  function selectDiv(div) {
    div.addClass('quickSearchResultDivSelected');
    $('#details', div).slideDown(quickSearchConfig.resultAnimation);
  }

  function initSearch() {
    // first store query in data
    $(searchBox).data('query', $(searchBox).val());

    // clear the results
    $('#quickSearchResults').empty();

    // start the search
    var query = $(searchBox).val();
    if (query.length >= quickSearchConfig.minCharacters) {
      showResultsDiv('Searching ...'); // display status
      search(query);
    }
  }

  function search(query) {
    quickSearchSelectedDivIndex = -1;
    var queryXML =
      "<QueryPacket xmlns='urn:Microsoft.Search.Query' Revision='1000'> \
            <Query domain='QDomain'> \
             <SupportedFormats> \
                <Format>urn:Microsoft.Search.Response.Document.Document</Format> \
             </SupportedFormats> \
             <Context> \
              <QueryText language='en-US' type='STRING' >SCOPE:\"" +
      quickSearchConfig.scope +
      '"' +
      query +
      "</QueryText> \
             </Context> \
             <SortByProperties> \
               <SortByProperty name='Rank' direction='Descending' order='1'/> \
             </SortByProperties> \
             <Range><StartAt>1</StartAt><Count>" +
      quickSearchConfig.numberOfResults +
      '</Count></Range> \
             <EnableStemming>false</EnableStemming> \
             <TrimDuplicates>true</TrimDuplicates> \
             <IgnoreAllNoiseQuery>true</IgnoreAllNoiseQuery> \
             <ImplicitAndBehavior>true</ImplicitAndBehavior> \
             <IncludeRelevanceResults>true</IncludeRelevanceResults> \
             <IncludeSpecialTermResults>true</IncludeSpecialTermResults> \
             <IncludeHighConfidenceResults>true</IncludeHighConfidenceResults> \
            </Query></QueryPacket>';

    var soapEnv =
      "<soap:Envelope xmlns:xsi='https://www.w3.org/2001/XMLSchema-instance'" +
      " xmlns:xsd='https://www.w3.org/2001/XMLSchema' \
              xmlns:soap='https://schemas.xmlsoap.org/soap/envelope/'> \
              <soap:Body> \
                <Query xmlns='urn:Microsoft.Search'> \
                  <queryXml>" +
      escapeHTML(queryXML) +
      '</queryXml> \
                </Query> \
              </soap:Body> \
            </soap:Envelope>';

    $.ajax({
      url: L_Menu_BaseUrl + '/_vti_bin/search.asmx',
      type: 'POST',
      dataType: 'xml',
      data: soapEnv,
      complete: processResult,
      contentType: 'text/xml; charset="utf-8"',
    });

    function processResult(xData, status) {
      var html = '';
      $(xData.responseXML)
        .find('QueryResult')
        .each(function () {
          var divWidth = $(searchBox).parent().width() + 20;

          var x = $('<xml>' + $(this).text() + '</xml>');
          x.find('Document').each(function () {
            var title = $('Title', $(this)).text();
            var url = $('Action>LinkUrl', $(this)).text();
            var description = $('Description', $(this)).text();

            html +=
              "<div class='quickSearchResultDivUnselected' style='width:" +
              divWidth +
              'px;max-width:' +
              divWidth +
              'px\'> \
                            <a href="' +
              url +
              '">' +
              $('Title', $(this)).text() +
              "</a> \
                            <div style='display:none' id='details' style='margin-left:10px'>" +
              description +
              '<br/>' +
              url +
              ' \
                            </div> \
                        </div>';
          });
          if (x.find('TotalAvailable').text() != '')
            html +=
              "<div style='text-align:center; width:" +
              divWidth +
              "px'>Total results: " +
              x.find('TotalAvailable').text() +
              '</div>';
          else
            html +=
              "<div style='text-align:center; width:" +
              divWidth +
              "px'>Total results: 0</div>";
        });

      $('#quickSearchResults').empty().append(html);
      $('#quickSearchResults>div>a').hover(
        function () {
          selectDiv($(this).parent());
        },
        function () {
          unSelectDiv($(this).parent());
        }
      );
      showResultsDiv();
    }
  }

  function escapeHTML(str) {
    return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;');
  }
</script>
Author
Clavin Fernandes Developer Relations and Support Services

Clavin is a Microsoft Business Applications MVP who supports 1,000+ high-level enterprise customers with challenges related to PDF conversion in combination with SharePoint on-premises Office 365, Azure, Nintex, K2, and Power Platform mostly no-code solutions.

Explore related topics

Share post
Free trial Ready to get started?
Free trial