Customized jQuery UI Autocomplete Widget: List Items with description

jQuery has made DOM parsing & manipulation easy for every developer out there.

jQuery has provided a range of rich set of UI controls. My focus will be on how to customize the jQuery UI Autocomplete (version 1.8.15) widget item list according to our needs. Our requirement for the customized item list were:

Each Drop-down item should have a description beneath it.
Item should appear in bold face & it’s description should be small.

By default, the jQuery UI Autocomplete appears like this:

Figure1: Default appearance of the Autocomplete widget.

and we want to transform it to this:

Figure2: Customized Autocomplete widget with item list visible.

The demo accompanied with the jQuery UI download uses the code below to display results in Figure1:

[code language=”js”]
$(function() {
var availableTags = [
"ActionScript",
"AppleScript",
"Asp",
"BASIC",
"C",
"C++",
"Clojure",
"COBOL",
"ColdFusion",
"Erlang",
"Fortran",
"Groovy",
"Haskell",
"Java",
"JavaScript",
"Lisp",
"Perl",
"PHP",
"Python",
"Ruby",
"Scala",
"Scheme"
];
$( "#tags" ).autocomplete({
source: availableTags
});
});

[/code]

The id “tags” is attached to the input tag.

This code is pretty simple & self explanatory. Isn’t it?

Coming back to our requirements, we have to display a description for each item.

For this, there may be many more better ways to do it but I opted to add a separator to each item and then split()‘ed the item text from the separator if it had parentheses. Then, I wrapped each of two items in tags (you can use div if you like) so that I can change their style accordingly.

The separator can be any punctuation mark, say an hyphen (-), a pipe (|) etc. I went with the small opening parenthesis ‘(‘ because the list I received has already it’s description part inside these parentheses.

Customizing the Autocomplete Plug-in (Not Really):

Now, this is what I call the meat of this post.

Note: Instead of modifying the original source, its always a better idea to copy that part from the original source to a new place and do required changes.

jQuery has built into it, a low-level function called .data() which stores key-value-pair (KVP) data and associates it with a given element. The element and a key is passed to the .data() to retrieve the value stored with that key within the given element. check out official jQuery API documentation about .data() for a detailed explanation with code examples.

When we type in to the Autocomplete widget’s textbox, a dozen of private & public functions/events are called to search, filter, render and finally, display the list on the screen. Of all those functions, _renderItem() function and the select()  event are of interest because these are the places where we can override stuff.

You should already know that the  _ (underscore) prefix to a function tells that the function is private.

The original code looks like this:

[code language=”js”]
/*
* jquery.ui.autocomplete.js
* Line Number: 449
*
*/
_renderItem: function( ul, item) {
return $( "<li></li>" )
.data( "item.autocomplete", item )
.append( $( "<a></a>" ).text( item.label ) )
.appendTo( ul );
}

[/code]

Check out Git Hub source.

Lets now customize or should I better say override the _renderItem() function:

[code language=”js”]

_renderItem = function(ul, item) {
itemTitleDesc = split(item.value);
var desc = "";
if (is_array(itemTitleDesc)) {
itemTitle = itemTitleDesc[0];
if (itemTitleDesc.length > 1) {
itemDesc = itemTitleDesc[1];
itemDesc = itemDesc.replace(")", "");
desc = ‘<br/><span><small>’ + itemDesc + ‘</small></span>’;
}
var itemContent = ‘<span><strong>’ + itemTitle
+ ‘</strong></span>’ + desc;
return $("<li></li>").data("item.autocomplete", item).append(
$("<a></a>").html(itemContent)).appendTo(ul);
} else {
return $("<li></li>").data("item.autocomplete", item).append(
$("<a></a>").html(item.label)).appendTo(ul);
}
};
[/code]

Let me now explain the code to understand better what is happening. I won’t be getting into deeper details though.

For our case, whenever the function receives an item, it has a value of the form [tile] ( [description] ), therefore, we always want to break that into two parts from the first opening round bracket (parenthesis) and then get rid of the closing bracket. Obviously, the first & the second part of the string will serve as the title and description, for the items in the list. Then wrap each of them into their own span or div. All of this stuff is stored in the itemContent variable.

So, that was for the items in the list, now if we type in the text box, we get the nice list of items with their descriptions but we are not done yet because as soon as we select an item from the list, the text box would just be filled with the whole string including the parentheses because we haven’t told jQuery to what to do when a user ‘select’s an item.

[code language=”js”]
select : function(event, ui) {
itemTitleDesc = split(ui.item.value);
if (is_array(itemTitleDesc)) {
itemTitle = itemTitleDesc[0];
if (itemTitleDesc.length > 1) {
itemDesc = itemTitleDesc[1];
itemDesc = itemDesc.replace(")", "");
}
ui.item.value = itemTitle;
}
}
[/code]

Now, lets just put everything together:

[code language=”js”]

$("#tags").autocomplete({
source : availableTags,
select : function(event, ui) {
itemTitleDesc = split(ui.item.value);
if (is_array(itemTitleDesc)) {
itemTitle = itemTitleDesc[0];
if (itemTitleDesc.length > 1) {
itemDesc = itemTitleDesc[1];
itemDesc = itemDesc.replace(")", "");
}
ui.item.value = itemTitle;
}
}
}).data("autocomplete")._renderItem = function(ul, item) {
itemTitleDesc = split(item.value);
var desc = "";
if (is_array(itemTitleDesc)) {
itemTitle = itemTitleDesc[0];
if (itemTitleDesc.length > 1) {
itemDesc = itemTitleDesc[1];
itemDesc = itemDesc.replace(")", "");
desc = ‘<br/><span><small>’ + itemDesc +
‘</small></span>’;
}
var itemContent = ‘<span><strong>’ + itemTitle +
‘</strong></span>’ + desc;
return $("<li></li>")
.data("item.autocomplete", item)
.append($("<a></a>").html(itemContent))
.appendTo(ul);
} else {
return $("<li></li>")
.data("item.autocomplete", item)
.append($("<a></a>").html(item.label))
.appendTo(ul);
}
};
});

[/code]

Here are the two utility functions which I used to check for array and the other that splits the string.

Google gave me this. How lazy I am 😉

[code language=”js”]

function is_array(input) {
$ret = typeof (input) == ‘object’ && (input instanceof Array);
return $ret;
}

function split(val) {
return val.split("(");
};

[/code]

I hope this article serves it’s purpose.

Please do give your feedback.

Regads.