Friday, September 26, 2014

Twitter typeahead.js with BootStrap 3, AJAX, and CoffeeScript

I ran into a few problems today while deploying Twitter's typeahead.js.  I had dynamic typeahead data that I needed to pull from a URL (like an AJAX request), and I also needed to integrate typeahead with Bootstrap 3 (which breaks almost all of TypeAhead's CSS, and screws up bootstrap's form layout).  I also wanted to use BloodHound but have it search any part of the word (not just the beginning of the word), and have typeahead.js submit the form when an item was selected by clicking.  This project's JavaScript is actually written as CoffeeScript, so the last thing I had to do was convert working JavaScript to CoffeeScript.

The first thing I sorted out was the CSS.  Adding the following corrected all of my CSS issues (note: not all of this is CSS I wrote; much of it was pulled from various other BootStrap/typeahead solutions online, but it is tweaked as none of those worked 100% for me):

/* Twitter typeahead compatibility fixes */
.twitter-typeahead {
  float: left;
  margin-right: 3px;
}

.tt-suggestion {
  display: block;
  padding: 3px 20px;
}

.twitter-typeahead .tt-hint {
  color:#a1a1a1;
  padding: 6px 12px;
  border:1px solid transparent;
}

.twitter-typeahead .tt-query {
  border-radius: 4px!important;
  border-top-right-radius: 0!important;
  border-bottom-right-radius: 0!important;
}

.tt-dropdown-menu {
  min-width: 160px;
  margin-top: 2px;
  padding: 5px 0;
  background-color: #fff;
  border: 1px solid #ccc;
  border: 1px solid rgba(0,0,0,.2);
  *border-right-width: 2px;
  *border-bottom-width: 2px;
  -webkit-border-radius: 6px;
  -moz-border-radius: 6px;
  border-radius: 6px;
  -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
  -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
  box-shadow: 0 5px 10px rgba(0,0,0,.2);
  -webkit-background-clip: padding-box;
  -moz-background-clip: padding;
  background-clip: padding-box;
}

.tt-cursor {
  cursor: pointer;
  color: #fff;
  background-color: #0081c2;
  background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
  background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
  background-image: -o-linear-gradient(top, #0088cc, #0077b3);
  background-image: linear-gradient(to bottom, #0088cc, #0077b3);
  background-repeat: repeat-x;
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)
}

.tt-suggestion.tt-is-under-cursor a {
  color: #fff;
}

.tt-suggestion p {
  margin: 0;
}
After my form was looking normal again, I had to get typeahead working. After a lot of trial and error and converting it to CoffeeScript, here is what I had (my API URL was at /v1/devices/list). Another note, not all of this is my original code, it is bits and pieces of javascript from around the web which I cobbled together and converted to CoffeeScript:

typeahead = ->
  devices = new Bloodhound({
    datumTokenizer: (d) ->
      test = Bloodhound.tokenizers.whitespace(d.value)
      $.each(test, (k,v) ->
        i = 0
        while( (i+1) < v.length )
          test.push(v.substr(i,v.length))
          i++
      )
      return test
    ,
    queryTokenizer: Bloodhound.tokenizers.whitespace,
    limit: 10,
    prefetch: {
      url: '/v1/devices/list',
      filter: (list) ->
        $.map(list, (device) -> { value: device })
    }
  })

  # kicks off the loading/processing of `local` and `prefetch`
  devices.clearPrefetchCache()
  devices.initialize()


  # passing in `null` for the `options` arguments will result in the default
  # options being used
  $('.typeahead').typeahead(
    { 
      highlight: true,
    },
    { 
      name: 'devices',
      # `ttAdapter` wraps the suggestion engine in an adapter that
      # is compatible with the typeahead jQuery plugin
      source: devices.ttAdapter()
    },
  )
  $('input.typeahead').bind("typeahead:selected", -> $("form").submit() )

I hope this helps someone else out there!