v0.10.0 of typeahead.js ended up being almost a complete rewrite. Many things
stayed the same, but there were a handful of changes you need to be aware of
if you plan on upgrading from an older version. This document aims to call out
those changes and explain what you need to do in order to have an painless
upgrade.
In v0.10.0, the first argument to jQuery#typeahead
is an options hash that
can be used to configure the behavior of the typeahead. This is in contrast
to previous versions where jQuery#typeahead
expected just a series of datasets
to be passed to it:
// pre-v0.10.0
$('.typeahead').typeahead(myDataset);
// v0.10.0
$('.typeahead').typeahead({
highlight: true,
hint: false
}, myDataset);
If you're fine with the default configuration, you can just pass null
as the
first argument:
$('.typeahead').typeahead(null, myDataset);
The most notable change in v0.10.0 is that typeahead.js has been decomposed into
a suggestion engine and a UI view. As part of this change, the way you configure
datasets has changed. Previously, a dataset config would have looked like:
{
valueKey: 'num',
local: [{ num: 'one' }, { num: 'two' }, { num: 'three' }],
prefetch: '/prefetch',
remote: '/remote?q=%QUERY'
}
In v0.10.0, an equivalent dataset config would look like:
{
displayKey: 'num',
source: mySource
}
As you can see, local
, prefetch
, and remote
are no longer defined at the
dataset level. Instead, all you set in a dataset config is source
. source
is
expected to be a function with the signature function(query, callback)
. When a
typeahead's query changes, suggestions will be requested from source
. It's
expected source
will compute the suggestion set and invoke callback
with an array
of suggestion objects. The typeahead will then go on to render those suggestions.
If you're wondering if you can still configure local
, prefetch
, andremote
, don't worry, that's where the Bloodhound suggestion engine comes in.
Here's how you would define mySource
which was referenced in the previous
code snippet:
var mySource = new Bloodhound({
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.num);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: [{ num: 'one' }, { num: 'two' }, { num: 'three' }],
prefetch: '/prefetch',
remote: '/remote?q=%QUERY'
});
// this kicks off the loading and processing of local and prefetch data
// the suggestion engine will be useless until it is initialized
mySource.initialize();
In the above snippet, a Bloodhound suggestion engine is initialized and that's
what will be used as the source of your dataset. There's still one last thing
that needs to be done before you can use a Bloodhound suggestion engine as the
source of a dataset. Because datasets expect source
to be function, the
Bloodhound instance needs to be wrapped in an adapter so it can meet that
expectation.
mySource = mySource.ttAdapter();
Put it all together:
var mySource = new Bloodhound({
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.num);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: [{ num: 'one' }, { num: 'two' }, { num: 'three' }],
prefetch: '/prefetch',
remote: '/remote?q=%QUERY'
});
mySource.initialize();
$('.typeahead').typeahead(null, {
displayKey: 'num',
source: mySource.ttAdapter()
});
The Bloodhound suggestion engine is token-based, so how datums and queries are
tokenized plays a vital role in the quality of search results. Pre-v0.10.0,
it was not possible to configure the tokenization method. Starting in v0.10.0,
you must specify how you want datums and queries tokenized.
The most common tokenization methods split a given string on whitespace or
non-word characters. Bloodhound provides implementations for those methods
out of the box:
// returns ['one', 'two', 'twenty-five']
Bloodhound.tokenizers.whitespace(' one two twenty-five');
// returns ['one', 'two', 'twenty', 'five']
Bloodhound.tokenizers.nonword(' one two twenty-five');
For query tokenization, you'll probably want to use one of the above methods.
For datum tokenization, this is where you may want to do something a tad bit
more advanced.
For datums, sometimes you want tokens to be dervied from more than one property.
For example, if you were building a search engine for GitHub repositories, it'd
probably be wise to have tokens derived from the repo's name, owner, and
primary language:
var repos = [
{ name: 'example', owner: 'John Doe', language: 'JavaScript' },
{ name: 'another example', owner: 'Joe Doe', language: 'Scala' }
];
function customTokenizer(datum) {
var nameTokens = Bloodhound.tokenizers.whitespace(datum.name);
var ownerTokens = Bloodhound.tokenizers.whitespace(datum.owner);
var languageTokens = Bloodhound.tokenizers.whitespace(datum.language);
return nameTokens.concat(ownerTokens).concat(languageTokens);
}
There may also be the scenario where you want datum tokenization to be performed
on the backend. The best way to do that is to just add a property to your datums
that contains those tokens. You can then provide a tokenizer that just returns
the already existing tokens:
var sports = [
{ value: 'football', tokens: ['football', 'pigskin'] },
{ value: 'basketball', tokens: ['basketball', 'bball'] }
];
function customTokenizer(datum) { return datum.tokens; }
There are plenty of other ways you could go about tokenizing datums, it really
just depends on what you are trying to accomplish.
Dropping support for string datums was a difficult choice, but in the end it
made sense for a number of reasons. If you still want to hydrate the suggestion
engine with string datums, you'll need to use the filter
function:
var engine = new Bloodhound({
prefetch: {
url: '/data',
filter: function(data) {
// assume data is an array of strings e.g. ['one', 'two', 'three']
return $.map(data, function(str) { return { value: str }; });
},
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.value);
},
queryTokenizer: Bloodhound.tokenizers.whitespace
}
});
In previous versions of typeahead.js, you could specify a string template along
with the templating engine that should be used to compile/render it. In
v0.10.0, you can no longer specify templating engines; instead you must provide
precompiled templates. Precompiled templates are functions that take one
argument: the context the template should be rendered with.
Most of the popular templating engines allow for the creation of precompiled
templates. For example, you can generate one using Handlebars by doing the
following:
var precompiledTemplate = Handlebars.compile('<p>{{value}}</p>');
tt-is-under-cursor
is now tt-cursor
- Applied to a hovered-on suggestion (either via cursor or arrow key).
tt-query
is now tt-input
- Applied to the typeahead input field.
If something is missing from this migration guide, pull requests are accepted :)