I recently had the requirement to have a suggestion box beneath the Title field on a new item form for a SharePoint list.
Basically when a user submits a new item to a list, as he is typing in the title we want to display all other items in that list with similar names. Reason for this being people are submitting Ideas to the system and we don't want multiple ideas with the same purpose.
I initially used the standard SharePoint Lists service to get a list of items and then tried to find matching words - but I found that using the SharePoint search service does a better job in matching results.
I found a blog post on an async search box which does most of what I need by Jan Tielens (http://weblogs.asp.net/jan) and just made some adjustments. Below is the code I used on my page:
HTML---------------------------------------------------------------------------------------
<div id="quickSearchResults" style="display: none;"></div><style type="text/css">
#quickSearchResultsHeader{
line-height:20px;
font-size:16px;
}
#quickSearchResults{
border:solid 1px gray;
padding:10px;
line-height:20px;
}
</style>
JavaScript
---------------------------------------------------------------------------------------
// *** 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
numberOfResults: 5, // 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 searchingTextBox = $("input[title='Title']");
$(document).ready(function () {
searchingTextBox.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 (searchingTextBox.data("query") != searchingTextBox.val()) {
if (quickSearchTimer != null) // cancel the delayed event
clearTimeout(quickSearchTimer);
quickSearchTimer = setTimeout(function () { // delay the searching
$("#quickSearchResults").fadeOut(200, initSearch);
}, quickSearchConfig.delay);
}
});
});
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
searchingTextBox.data("query", searchingTextBox.val());
// clear the results
$("#quickSearchResults").empty();
// start the search
var query = searchingTextBox.val();
if (query.length >= quickSearchConfig.minCharacters) {
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' >contenttype:Idea " + 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='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'> \
<soap:Body> \
<Query xmlns='urn:Microsoft.Search'> \
<queryXml>" + escapeHTML(queryXML) + "</queryXml> \
</Query> \
</soap:Body> \
</soap:Envelope>";
$.ajax({
url: "/_vti_bin/search.asmx",
type: "POST",
dataType: "xml",
data: soapEnv,
complete: processResult,
contentType: "text/xml; charset=\"utf-8\""
});
function processResult(xData, status) {
var xml = "<xml>" + $(xData.responseText).find("QueryResult").text() + "</xml>";
var xmldoc = new ActiveXObject("Microsoft.XMLDOM");
xmldoc.loadXML(xml);
var itemElements = xmldoc.getElementsByTagName("Document");
var srHtml = "";
if (itemElements.length > 0) {
srHtml = "<div id=\"quickSearchResultsHeader\">Similar Ideas</div><div>";
for (var i = 0; i < itemElements.length; i++) {
var srUrl = $(itemElements[i].getElementsByTagName("LinkUrl")).text();
if (srUrl.indexOf('/Lists/Ideas') > 0) { // limits the results to one specific list
var srTitle = $(itemElements[i].getElementsByTagName("Title")).text();
srHtml = srHtml + "<a href=\"" + srUrl + "\">" + srTitle + "</a><br/>";
}
}
srHtml = srHtml + "</div>";
$("#quickSearchResults").html(srHtml);
$("#quickSearchResults").slideDown(200);
}
}
}
function escapeHTML(str) {
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}