As part of a new UI library, I have put together an autocomplete feature using the jQuery UI Autocomplete in a manner that follows the guidelines found in the Webgrown Solutions UI Manifesto. The Autocomplete supports the following features:
A google search will be made on the person name selected.
Just a basic page <input type="text"> element, with a normal class attribute and some custom data (data-*) attributes to allow feature customization. Once the CSS and Script stuff are in place on a website, any page can implement the Autocomplete by simply setting the input element with the class attribute to "autocomplete", and setting the desired custom data attributes.
That sounds like a DRY solution to me (see UI Manifesto).
<h3>Simple Display, With No Highlighting</h3> <p> <label for="txtAutocompleteDemo1">Person:</label> <input type="text" id="txtAutocompleteDemo1" class="autocomplete" data-serviceRequestUrl="/WebServices/DemoClientService.svc/PersonAutocomplete1" data-serviceRequestDelay="300" data-serviceRequestMinLength="2" data-highlightMatches="false" /> </p> <h3>Simple Display, With Highlighting</h3> <p> <label for="txtAutocompleteDemo2">Person:</label> <input type="text" id="txtAutocompleteDemo2" class="autocomplete" data-serviceRequestUrl="/WebServices/DemoClientService.svc/PersonAutocomplete1" data-serviceRequestDelay="300" data-serviceRequestMinLength="2" data-highlightMatches="true" /> </p> <h3>Templated Display, With No Highlighting</h3> <p> <script id="personTemplate" type="text/x-jquery-tmpl"> <div class="itemName">${FullName}</div> <div class="itemDetails">${City}, ${StateAbbreviated} ${ZipCode}</div> </script> <label for="txtAutocompleteDemo3">Person:</label> <input type="text" id="txtAutocompleteDemo3" class="autocomplete" data-serviceRequestUrl="/WebServices/DemoClientService.svc/PersonAutocomplete2" data-serviceRequestDelay="300" data-serviceRequestMinLength="2" data-itemTemplateId="personTemplate" data-propertyToUseOnSelect="FullName" data-highlightMatches="false" /> </p> <h3>Templated Display, With Highlighting & Select Action</h3> <p>A google search will be made on the person name selected.</p> <label for="txtAutocompleteDemo4">Person:</label> <input type="text" id="txtAutocompleteDemo4" class="autocomplete" data-serviceRequestUrl="/WebServices/DemoClientService.svc/PersonAutocomplete2" data-serviceRequestDelay="300" data-serviceRequestMinLength="2" data-itemTemplateId="personTemplate" data-propertyToUseOnSelect="FullName" data-highlightMatches="true" data-selectActionFunction="googlePerson" /> </p>
//Enable Autocomplete(s) lastErroredCallTrace += "->autocomplete"; $(parentElementSelector + " .autocomplete").each(function () { var tempThisVar = $(this); self.setTimeout(function () { enableFeature_Autocomplete(tempThisVar); }, 1); }); function enableFeature_Autocomplete(targetElement) { lastErroredCallTrace += "->enableFeature_Autocomplete"; //Check to see if already enabled. if (!isAttrDefined(targetElement.attr("data-autocompleteEnabled"))) { targetElement.autocomplete({ //source: targetElement.attr("data-serviceRequestUrl"), source: function (request, response) { lastErroredFunctionName = "common.js -> $(parentElementSelector + .autocomplete).each(function () {"; lastErroredRequestingElement = targetElement; lastErroredServiceRequestUrl = this.element.attr("data-serviceRequestUrl"); lastErroredJsonToPost = "{ \"term\":\"" + request.term + "\"}"; makesXhr = $.getJSON(this.element.attr("data-serviceRequestUrl"), { term: request.term }, function (data, textStatus, jqXHR) { if (textStatus == "success") { var dataTemp; if (data.d == undefined) { dataTemp = data; } else { //WCF sends back with the dumb data.d as a string dataTemp = $.trim(data.d); dataTemp = $.parseJSON(dataTemp); } response(dataTemp); } else { errorOnAjaxRequest(jqXHR, textStatus, "Error on Ajax Complete"); } }); }, open: function (event, ui) { if (!targetElement.is(":focus")) { targetElement.delay(500).autocomplete("close"); } }, minLength: ((!isAttrDefined(targetElement.attr("data-serviceRequestMinLength")) || isNaN(targetElement.attr("data-serviceRequestMinLength"))) ? 2 : eval(targetElement.attr("data-serviceRequestMinLength"))), delay: ((!isAttrDefined(targetElement.attr("data-serviceRequestDelay")) || isNaN(targetElement.attr("data-serviceRequestDelay"))) ? 300 : eval(targetElement.attr("data-serviceRequestDelay"))), select: function (event, ui) { if (isAttrDefined(targetElement.attr("data-propertyToUseOnSelect")) && targetElement.attr("data-propertyToUseOnSelect").length > 0) { targetElement.val(ui.item[targetElement.attr("data-propertyToUseOnSelect")]); } else { targetElement.val(ui.item.value); } if (isAttrDefined(targetElement.attr("data-selectActionFunction")) && targetElement.attr("data-selectActionFunction").length > 0) { callFunctionDynamically(targetElement.attr("data-selectActionFunction"), targetElement, ui.item); } return false; } }) .data("autocomplete")._renderItem = function (ul, item) { if (isAttrDefined(this.element.attr("data-itemTemplateId")) && this.element.attr("data-itemTemplateId").length > 0) { var itemTemplate = $("#" + this.element.attr("data-itemTemplateId")).html(); var itemVariables = itemTemplate.match(/\${([^}]*)}/gi); //Get "${...} vars. for (x in itemVariables) { if (x >= 0) { var itemVariableFull = itemVariables[x]; var itemVariableNameOnly = itemVariableFull.substr(2, (itemVariableFull.length - 3)); var itemVariableValue = item[itemVariableNameOnly]; if (itemVariableValue != undefined && itemVariableValue.length > 0) { if (this.element.attr("data-highlightMatches") == "true") { var regExp = new RegExp(this.term, "i"); var match = regExp.exec(itemVariableValue); itemVariableValue = itemVariableValue.replace(regExp, markOpenTag + match + markCloseTag); itemTemplate = itemTemplate.replace(itemVariableFull, itemVariableValue); } else { itemTemplate = itemTemplate.replace(itemVariableFull, itemVariableValue); } } } } } else { if (this.element.attr("data-highlightMatches") == "true") { var regExp = new RegExp(this.term, "i"); var match = regExp.exec(item.label); itemTemplate = item.label.replace(regExp, markOpenTag + match + markCloseTag); } else { itemTemplate = item.label; } } var anchorElement = document.createElement("a"); anchorElement.className = "itemWrapper clearFix"; anchorElement.innerHTML = itemTemplate; return $(liElementConstructor) .data("item.autocomplete", item) .append(anchorElement) .appendTo(ul); }; //Mark as enabled. targetElement.attr("data-autocompleteEnabled", true); } }
/* General Autocomplete */ ul.ui-autocomplete li { border-top:1px dashed #CCC; } ul.ui-autocomplete li:first-child { border-top-width:0px; } ul.ui-autocomplete mark { background-color:Yellow; font-style:inherit; font-weight:inherit; } /* Make Autocomplete Scrollable */ .ui-autocomplete { max-height:300px; overflow-y:auto; /* prevent horizontal scrollbar */ overflow-x:hidden; /* add padding to account for vertical scrollbar */ padding-right:20px; } * html .ui-autocomplete { height:300px; } <!-- The jQuery UI styling of your choice --> <link href="jquery-ui.css" rel="stylesheet" type="text/css" />