Getting More From Twitter Bootstrap’s Typeahead Library

Twitter Bootstrap comes packaged with a simple auto-complete library that’s stylistically integrated into the CSS framework. It’s convenient, but not the most-documented part of Bootstrap. I was working on a hobby project recently, and I wanted to have an auto-completing search field, but it took a bit of work to extend the basic implementation.

The first, and probably most important, step was to implement an AJAX data source. The plugin supports this out of the box, but the documentation page doesn’t have an example. It’s easy enough. You pass a function that takes the query and hands it off to one of jQuery’s magic asynchronous request functions, like so:

$(document).ready(function() {
	$("#searchfield").typeahead({
		minLength: 3,
		source: function(query, process) {
			$.post('/search/typeahead', { q: query, limit: 8 }, function(data) {
			 	process(JSON.parse(data));
			});
		}
	});
});

Your request URL needs to respond with a JSON array of strings, which the typeahead library will process. The variables “q” and “limit” are sent along with the POST request in this example, and a PHP back-end returns the relevant data in response.

["Electric Light Orchestra", "Elvis Costello", "Eric Clapton"]

Once I had the asynchronous search working, I decided that it would be better if the user was taken to the search page as soon as they selected a suggested item, rather than having them click a search button. So I hooked in to the updater method, which runs whenever an item is selected.

$(document).ready(function() {
	$("#searchfield").typeahead({
		minLength: 3,
		source: function(query, process) {
			$.post('/search/typeahead', { q: query, limit: 8 }, function(data) {
			 	process(JSON.parse(data));
			});
		},
		updater: function (item) {
			document.location = "/search?q=" + encodeURIComponent(item);
			return item;
		}
	});
});

It’s just a simple matter of intercepting the selected item and jumping to a new page, passing the search string in the query. As far as I can tell, updater is not documented at all on the Bootstrap site.

Now…what if a user doesn’t want to search for one of the suggestions? In its present form, the typeahead doesn’t give much choice. If it finds a suggestion, it’s automatically selected. While this is good for some situations, it might not make sense for some. My solution was to dynamically insert an additional item at the top of the suggestions list, matching the search query. The sorter function worked well for that, as it runs whenever the list is updated.

$(document).ready(function() {
	$("#searchfield").typeahead({
		minLength: 3,
		source: function(query, process) {
			$.post('/search/typeahead', { q: query, limit: 8 }, function(data) {
			 	process(JSON.parse(data));
			});
		},
		updater: function (item) {
			document.location = "/search?q=" + encodeURIComponent(item);
			return item;
		},
		sorter: function (items) {
			items.unshift(this.query);
			return items;
		}
	});
});

You just slip the query into the items array with unshift and return the array with no further tampering.

  • Stuart

    Thanks for the quick primer, wish the docs had this info in it!

  • http://bareboneswebhosting.com/ Brian Bilotte

    I’m with Stuart, there’s so much Bootstrap can do that I haven’t even come close to figuring out yet!

  • Steve

    Great post, Matt. Is your search page live anywhere so we can see the final result?

    • http://www.webmaster-source.com Matt

      You can see it in action here: http://lotrotunes.clickmadly.com/

      Type a song title into the search field, and it will return suggestions for any it can match.

      • Steve

        Nice one – thanks

  • Steve

    The updater override was exactly what I needed. Thanks for posting this!

  • Todd

    This is great code! But I can’t figure out how to make it work with json name, id data: [{"id":1528634,"name":"Tree Mount Screws 5 Pack"}]. I need to do it this way so I can pass the id to a product detail page.
    Any ideas? – Thanks!

    • http://www.webmaster-source.com Matt

      The Bootstrap library only accepts a flat array. You might be able to get it to work by parsing the response and storing the object, then creating a second array with just the names to feed to the process() method. You could then compare “item” against the original object in the updater method.

      • Todd

        I got it to work with the help of this article: http://tatiyants.com/how-to-us.....typeahead/

        $(‘#mainsearch’).typeahead({
        minLength: 4,
        source: function (query, process) {
        products = [];
        map = {};
        $.ajax({
        type: “get”,
        url: “/includes/ajax/autocomplete-keyword-search?”,
        data: “q=” + query,
        cache: false,
        dataType: ‘json’,
        success: function (data) {
        $.each(data, function (i, product) {
        map[product.name] = product;
        products.push(product.name);
        map[product.sku] = product;
        products.push(product.sku);
        map[product.vendor] = product;
        products.push(product.vendor);
        });
        return process(products);
        }
        });
        },
        updater: function (item) {
        if (jQuery.type(map[item]) !== ‘undefined’){
        selectedProduct = map[item].id;
        //console.log(selectedProduct);
        document.location = “/search?id=” + encodeURIComponent(selectedProduct);
        }
        return item;
        },
        matcher: function (item) {
        if (item.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1) {
        return true;
        }
        },
        sorter: function (items) {
        items.unshift(this.query);
        return items;
        }
        });

        • http://www.webmaster-source.com Matt

          That’s a pretty good article. It would have saved me a bit of time if they had published it a week or two sooner, before I worked on the project that led to writing this post… :)

  • Derek

    Hi Matt. I’m looking for a way to do something very similar to the above, however when one clicks a result from the dropdown I’d like it to go directly to that result rather than a general search page. Do you have any idea how to achieve this? I’m a coder, but a little rusty trying to get back into web design.

    My site will be as follows: a google style search bar with autocomplete, a user searches ‘Lemon’ which will return certain titles in a dropdown e.g. ‘Lemons are really nice’ by searching a MySQL database for any occurrence of ‘Lemon’ in either the title or body (‘Lemons are nice because they are lemony.’) Pressing enter with a dropdown entry highlighted will take you directly to the ‘Lemon’ page containing the full body, clicking the search icon however will take you to a page with detailed search results.

    Make sense?

    I’d appreciate any input. Thanks!

  • HideMy

    Hi, you can also edit the template using the option item ex.
    item: ”

    is it possibile to make some LI selectable and some LI not-selectable?

    I would like to create a HEDER to divide the result..

    Tnx

  • https://www.sangahosting.co.uk Cameron

    Great post, thanks very much!

    I have copied your code but get an error (below) when deleting text using the backspace button.

    “SyntaxError: Unexpected token <"

    Anyone else getting this?

  • http://ebadanie.com Krzysztof Wiśniewski

    I’ve found in the Internet and use modified version in my project based on ASP.NET WebApi (MVC4) (web service gives me XML output):

    $(function () {
    $(‘#tags’).autocomplete({
    source: function (request, response) {
    $.ajax({
    url: ‘/api/City/GetReterCities’,
    type: ‘GET’,
    cache: false,
    data: request,
    dataType: ‘xml’,
    success: function (xmlResponse) {
    var data = $(‘ReterCity’, xmlResponse).map(function () {
    return {
    value: $(‘CityName’, this).text(),
    id: $(‘CityId’, this).text()
    };
    }).get();
    $(‘#tags’).autocomplete({
    source: data
    });
    },
    error: function (XMLHttpRequest, textStatus, errorThrown) {
    console.log(‘error’, textStatus, errorThrown);
    }
    });
    },
    select: function (event, ui) {
    alert(‘you have selected ‘ + ui.item.value + ‘ ID: ‘ + ui.item.id);
    $(‘#tags’).val(ui.item.label);
    return false;
    }
    })
    });

  • gfdfgdfg

    dfdsfsdf