Twitter Bootstrap provides an autocomplete/typeahead component which is very easy to use, but is a bit tricky to use (in my humble opinion) when you are working with anything more than a simple String array as your source of autocomplete options.
Say for example you want to allow users to search your server-based database of products based on the product name. The user types in the name of the product and typeahead goes off and makes an asynchronous request to the sever to query the database and returns a list of products. In the typeahead widget you want to show the user the list of potential product names, but when they select one of the options, you want to able to populate the display with the product price, SKU, description, etc. and of course you need the ID from the server so you can manage communications with the server, say for a purchase.
For starters though, let’s go over the basics of how typeahead works. The following example has 3 potential product names:
- Deluxe Bicycle
- Super Deluxe Trampoline
- Super Duper Scooter
Type in whatever partial name you want and you’ll see the appropriate typeahead options.
The code for that is fairly staightforward:
It’s simply an input tag with data-provide=”typeahead” to indicate that we want typeahead on the input tag, and then data-source specifies an array of strings.
Visually, this behaves identically. Try out the following text input by typing a substring of one of the product names:
But as you’ll see in the source, here we do not specify data-source in the input element declaration. Instead we call the typeahead() method on the input element and pass in the source option as a function which returns the string array.
If you wanted to retrieve the list of products from the server, in your source function override you would make your AJAX call, and then in the asynchronous response you would generate the string array and call process(), passing in the string array.
I also found that there was a bug in some browsers with the typeahead component when trying to select one of the typeahead options with the mouse. This is a known issue. Sometimes mouse selection doesn’t work. Rather than change the bootstrap-typeahead.js file, I’ve overwritten the typeahead blur() method to give it a slightly longer than normal delay.
Besides the source option, you can also specify a highlighter() function that specifies how each possible autocomplete option is displayed, and an updater() function that can execute some code when the user selects an item. In the following I’ve modified the display of each item using the highlighter() override, and I print out the selected item to the console in the overridden updater() method:
Now, the moment you’ve been waiting for. Well, almost. If you’ve stuck around this far, you’re not interested simply in an array of strings. Your data is more likely an array of objects. The following input should behave identically to the previous one, only this time we have an array of product objects. Each product object contains an id, a name and a price.
Here’s the code for that:
The main difference here, other than the declaration of the products array is in the source override. We generate an array of strings based off the name property of each product object. If you wanted to expand this example to retrieve your list of objects from the server, in your source function override you would make your AJAX call, and call process() on the results from the asynchronous response.
But that’s not really what I want. I want to display the name and price of each object in the list of typeahead options. And I want to display the selected item with all of its details.
What we need to do first is instead of having the source function return an array of strings, we’re going to have it return an array of product ids. Since the ids are numbers, and process() is expecting an array of strings, we convert each id to a string first.
The typeahead component is then going to call matcher() for every item, to make sure that, by default the original query string is a substring of the item. Since our item is simply the product id, that’s not going to work, so we need to override that. For simplicity sake, I just always return true, but you could use the product id to get the product from the list of products, and then check that product’s name against the query if you wanted.
Next, in the highlighter() method, I use Underscore’s find() method to find the product in the array of products based on the product id, and then display a combination of the product name and price.
Finally, in the updater() method, which is called when an item is selected, I find the selected product just as we did in highlighter(), but now I call that.setSelectedProduct() with the product name. that is defined using closure outside of the typeahead() call on the element, as is setSelectedProduct(). You could have setSelectedProduct’s functionality right in updater() but I thought it might be nice to pull it out of the updater() code to make it a little more reusable.
Last but not least updater() returns the product name, so that is what you are left with in the input element.
Here’s the working example:
and here is the full and final source:
Hopefully this should all make implementing real-world typeahead a little easier.