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" />