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.
Follow the steps outlined below to add Search as you Type to your site collection:
-
Download and install Muhimbi’s SharePoint Infuser on one of your Web Front End Servers.
-
Ensure you have Designer privileges, more specifically the Add and Customize Pages right.
-
From the Site Actions / Site Settings screen, select Infuse custom content in the Look and Feel column.
-
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.
-
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, '&') .replace(/</g, '<') .replace(/>/g, '>'); } </script>
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.