Twitter Bootstrap typeahead.js with underscore.js Templating – A Tutorial

In a previous post, I described how to use the Twitter Bootstrap Typeahead component. The Twitter Bootstrap team recently announced however that they would be switching to Twitter’s typeahead.js for the next major release. That sounds pretty great to me.  typeahead.js is much more powerful than the current Twitter Bootstrap typehead component. Fortunately, since typeahead.js is already available, you don’t have to wait for Twitter Bootstrap v3 to start using it. Using typeahead.js with Twitter Bootstrap isn’t very well documented just yet.  As well, there is no documentation to be found on how to use Underscore.js templates with typeahead.js, so I thought I’d help you along here.

We’re going to follow along very similarly to my original Twitter Bootstrap typeahead Tutorial, starting with simple examples and then making our way to more complex object-based data.

This is what we’ll ultimately be building:

Product Search

Start typing any of the products named ‘Deluxe Bicycle’, ‘Super Deluxe Trampoline’ or ‘Super Duper Scooter’ in the product name text input field above.  If you select one of the autocomplete options, the form should auto-fill with the ID and price of the product.

Let’s see how we build this…

Initial Setup

First, we want to reference bootstrap’s CSS and then follow it up with the CSS the typeahead.js folks generously provide for it to work nicely with Bootstrap:

<head>
   ...
   <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
   <link rel="stylesheet" href="css/typeahead.js-bootstrap.css">
</head>

You’ll need to make sure typeahead.js-bootstrap.css is declared after the normal bootstrap.css so it can override the CSS where necessary.

Now, just before your closing body tag, you’ll want to include jQuery.js, bootstrap.js and typeahead.js.  We’re also going to use underscore.js for templating later, so we might as well do that now. The typeahead.js documentation uses Hogan.js, but I’m used to and like underscore.js, so let’s stick with that.  It also gives me an opportunity to document something for which I could find no documentation at present:

<body>
   ...
   <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
   <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
   <script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script>
   <script src="js/typeahead.min.js"></script>
</body>

Let’s place the Form with the typeahead text input field somewhere in the body before the script declarations.  The text input form field is pretty straightforward:

<body>
   ...
   <form>
      <fieldset>
         <h3>Product Search</h3>
         <input type="text" placeholder="product name" class="product-typeahead">
      </fieldset>
   </form>
   ...
</body>

and let’s configure the input field for typeahead:

<body>
   ...
   $(function($) {
      $('input.product-typeahead').typeahead({
         name: 'products',
         header: '<h3>Products</h3>',
         local: [
            'Deluxe Bicycle', 
            'Super Deluxe Trampoline',
            'Super Duper Scooter'
         ]
      });
   });
</body>

Notice that we put this script at the bottom of the page so it is loaded after the previous Scripts all load.  We then wait for the Document to be ready before doing anything.

All we have to do is call the typeahead() method on the jQuery element and pass in a dataset definition.  You can define multiple datasets.  In this case we only have one dataset named ‘products’.  Each dataset can be dealt with independently.  There’s all kinds of great things you can do with datasets like accessing data remotely, prefetching and preprocessing data and much more.  In this simple case though, we’re just going to use some local data; a static array of the 3 product names.  We also set the header property to show some HTML at the top of the autocomplete suggestions.

This will produce the following:

Product Search

Give it a try!

Objects

Of course, having just a list of static strings isn’t that interesting.  Normally, you would have a list of objects that you’ve received from a remote service. Each object would typically include not only the product name, but perhaps its price and an ID that can be used for database persistence.  Let’s update the dataset accordingly:

<body>
   ...
   $(function($) {
      $('input.product-typeahead').typeahead({
         name: 'products',
         header: '<h3>Products</h3>',
         local: [
            {
               id: 0,
               name: 'Deluxe Bicycle', 
               price: 499.98
            },
            {
               id: 1,
               name: 'Super Deluxe Trampoline',
               price: 134.99
            },
            {
               id: 2,
               name: 'Super Duper Scooter',
               price: 49.95
            }
         ]
      });
   });
</body>

But we need to do one more thing here.  We need to tell typeahead.js the value to display for each object. We need to set the valueKey property to ‘name’ so it knows to look at the name property for each object. You can optionally specify an array of tokens here to help with searching, but by default it will split the value into tokens so let’s just leave that for now.

      $('input.product-typeahead').typeahead({
         name: 'products',
         header: '<h3>Products</h3>',
         valueKey: 'name',
         local: [
         ...

It would also be nice though, to not only show the product name, but also show its price in the autocomplete suggestions. We can do that with a template. We set the template property in the dataset to the following:

      $('input.product-typeahead').typeahead({
         name: 'products',
         header: '<h3>Products</h3>',
         valueKey: 'name',
         template: '<p><strong><%= name %></strong>:&nbsp;$<%= price %></p>',
         local: [
         ...

The template above is an Underscore.js template.  The typeahead.js docs show examples using Hogan.js, but we’re going to use Underscore.js templates.  Any templating engine can be used as long as it supports the expected API.  The templating engine needs to be able to create a compiled template by calling a compile(template) method on the engine.  Then, you need to be able to call render(context) on the compiled template.  We can easily configure Underscore.js to support this by executing the following JavaScript once when the document is ready:

$(function() {
   _.compile = function(templ) {
      var compiled = this.template(templ);
      compiled.render = function(ctx) {
         return this(ctx);
      }
      return compiled;
   }
   ...
});

Now we can set the dataset template engine to use Underscore by setting the engine property:

      $('input.product-typeahead').typeahead({
         name: 'products',
         header: '<h3>Products</h3>',
         valueKey: 'name',
         engine: _,
         template: '<p><strong><%= name %></strong>:&nbsp;$<%= price %></p>',
         local: [
         ...

Each object that matches the typed in characters will be passed as the context to the render() method of the template engine.  This dataset’s template extracts the name and price properties from the context to generate the appropriate HTML that provides feedback like the following:

Screen Shot 2013-06-04 at 9.41.17 PM

Now, in practice you’re going to want to do something with the actual values of the selected object.  For this exercise we’ll just show the values.

Let’s add a few more elements to the form to display the product ID & price:

<body>
   ...
   <form>
      <fieldset>
         <h3>Product Search</h3>
         <input type="text" placeholder="product name" class="product-typeahead">
         <label for="productID">Product ID: </label>
         <input type="text" id="productID" disabled>

         <label for="productPrice">Price: </label>
         <input type="text" id="productPrice" disabled>
      </fieldset>
   </form>
   ...
</body>

When a user selects one of the suggested autocomplete items, a typeahead:selected event is generated and the data for the selected item is passed to the event.

We can catch that event using the jQuery on() method as follows:

$('input.product-typeahead')
   .on('typeahead:selected', function(event, datum) {
   /* Do something with datum */
}

Here is the final full source that will set the product ID & price in the form when the user selects one of the options:

<html>
<head>
   <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
   <link rel="stylesheet" href="css/typeahead.js-bootstrap.css">
</head> 
<body>

   <div class="content">
      <form>
         <fieldset>
            <h2>Product Search</h2>
            <input type="text" placeholder="product name" 
                id="productName" class="product-typeahead">
            <label for="productID">Product ID: </label>
            <input type="text" id="productID" disabled>
            <label for="productPrice">Price: </label>
            <input type="text" id="productPrice" disabled>
         </fieldset>
      </form>
   </div>

   <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
   <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
   <script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script>
   <script src="js/typeahead.min.js"></script>
   <script>
   $(function($) {    
      _.compile = function(templ) {
         var compiled = this.template(templ);
         compiled.render = function(ctx) {
            return this(ctx);
         }
         return compiled;
      }
      $('.product-typeahead').typeahead({
         header: '<h3>Products</h3>',
         template: 
       '<p><strong><%= name %></strong>:&nbsp;$<%= price %></p>',
         name: 'products',
         valueKey: 'name',
         engine: _,
         local: [
            {
               id: 0,
               name: "Deluxe Bicycle",
               price: 499.98
            },
            {
               id: 1,
               name: "Super Deluxe Trampoline",
               price: 134.99
            },
            {
               id: 2,
               name: "Super Duper Scooter",
               price: 49.95
            }
         ]
      }).on('typeahead:selected', function(event, datum) {
         $('#productID').val(datum.id);
         $('#productPrice').val("$" + datum.price);
      });
   });
   </script>
</body>
</html>

If you want to try this out yourself you should be able to copy/paste the code above, though you’ll need to grab typeahead.js typeahead.js-bootstrap.css from the typeahead.js project.

And there you have it.  Scroll to the top if you want to see this full working demo again.

Happy typing ahead!

Tagged . Bookmark the permalink.

7 Responses to Twitter Bootstrap typeahead.js with underscore.js Templating – A Tutorial

  1. pjog says:

    Appreciate your effort!
    Any way to have a pull-down where the options come as soon as the user clicks on the input. And the typeahead will help to filter the options.

  2. Great tutorial- definitely cleared up some confusion! I’ve been looking for more bootstrap tutorials, but it’s hard to find ones that are well written AND useful. Can’t wait to look at your other tutorials.

  3. josh says:

    Great post!

    Can we use this to query .json non-locally?

    for example
    https://itunes.apple.com/search?termq=%QUERY&country=us&entity=software

    Thank you

  4. Ajay says:

    I am trying to pass dynamic value for header, but doesn’t seems to be working. It continue to display the value assigned first time. Any suggestion please. Thanks.

  5. No sure if adding functions to underscore is, in fact, overkill? It may be something which has been undated since you wrote the post, but a compiled underscore template is a drop in replacement for other templating engines, ie ;


    this.$('#user-name').typeahead(
    {},
    ...
    templates: {
    suggestion: _.template(' – ')
    }
    ]
    }

  6. Justin says:

    I think you only use _.template(“: $”)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>