Typeahead.js: Substrings only match at beginning of word

Created on 7 Mar 2013  路  15Comments  路  Source: twitter/typeahead.js

I don't know if this is on purpose, but substrings only match at the beginning of a word.
Searching for "ant" will find "antilope" or "ants" but not "giant" or "replicant".

Most helpful comment

Configurable!! Configurable!! =)

I just ended up here after googling 'twitter typeahead beginning of word'. I'm thinking even if it's not a search engine, typeahead could by all means aid me in navigating uncharted datasets. For instance, when autocompleting "China", I won't find a match in a dataset that has "Republic of China".

The dataset can then aid the visitor by providing thorough entries, such as "Football/Soccer" which would turn up if I typed "soccer".

/ My $0,05

All 15 comments

That's by design. Since typeahead.js isn't a search engine, it should be okay if it uses a somewhat naive algorithm to get suggestions.

Couldn't it be configurable?

Not sure if I'd want it to be configurable for the sake of simplicity, but I wouldn't be opposed to implementing a move advanced matching algorithm.

Configurable!! Configurable!! =)

I just ended up here after googling 'twitter typeahead beginning of word'. I'm thinking even if it's not a search engine, typeahead could by all means aid me in navigating uncharted datasets. For instance, when autocompleting "China", I won't find a match in a dataset that has "Republic of China".

The dataset can then aid the visitor by providing thorough entries, such as "Football/Soccer" which would turn up if I typed "soccer".

/ My $0,05

+1 for @o-o- reasoning for why typeahead should be configurable to match any part of the word. It also seems the autocomplete in Twitter Bootstrap does this type of filtering, so would seem weird that a library based on that is not able to do matches in any part of the word.

Actually I realized right after posting that one way of at least partially getting around the limitation is to use the tokens feature of the datums. So to match both China and Republic of China you'd create a datum object in the dataset looking like this:

...
local: [ {value:"Republic of China", tokens:["China", "Republic of China"] } ],
...

Probably not too much of an overhead to have some logic for generating the tokens automatically, especially if the dataset is generated server side (which it probably is in most cases is) and cached in the browser using prefetch.

But still, I think it's a valid use case to be able to configure typeahead to match any part of the word.

+1 on configuring a full versus naive search alogrithm for completion. While tokens certainly are a good work around for the use case you provide, it won't solve nearly as many cases a simple full search might.

Hello,

I need this functionality too. It looks like this is very easy to implement since SearchIndex could simply be replaced by a user-provided class that exposes some basic methods (add, get, reset, bootstrap, and serialize).

The referenced commit is the one line change, here's one way to actually use it. You need to create your own index class, like so:

var MyIndex = function() {
    function MyIndex(o) {
        o = o || {};
        if (!o.datumTokenizer || !o.queryTokenizer) {
            throw 'datumTokenizer and queryTokenizer are both required';
        }
        this.datumTokenizer = o.datumTokenizer;
        this.queryTokenizer = o.queryTokenizer;
        this.reset();
    }

    _.extend(MyIndex.prototype, {
        bootstrap: function(o) {
            this.data = o.data;
        },

        add: function(data) {
            data = _.isArray(data) ? data : [data];
            _.each(data, function(datum) {
                if (!datum) {
                    return;
                }
                var tokens = this.datumTokenizer(datum);
                if (!tokens.length) {
                    return;
                }
                this.data.push({
                    datum: datum,
                    tokens: tokens,
                });
            }, this);
        },

        get: function(query) {
            var token_regex = _.map(this.queryTokenizer(query), function(token) { return new RegExp(token, 'i'); }),
                matches;
            _.each(token_regex, function(regex) {
                var ids = _.chain(this.data).map(function(data, id) { return id; }).filter(function(id) {
                    return _.detect(this.data[id].tokens, function(t) {
                        return regex.test(t);
                    });
                }, this).value();
                if (!ids.length) {
                    return;
                }
                matches = matches ? _.intersection(matches, ids) : ids;
            }, this);
            return _.chain(matches).unique().map(function(id) { return this.data[id].datum; }, this).value();
        },

        reset: function() {
            this.data = [];
        },

        serialize: function() {
            return { data: this.data };
        }
    });

    return MyIndex;
}();

Then just pass it as index when initializing Bloodhound:

var bloodhound = new Bloodhound({
    ...
    index: MyIndex
 });

This lets me do those partial/mid-word and such searches.

hi @elad ,
Apologies for a very beginner question, but where do I insert this 'MyIndex' function?
I'm confused on whether putting it in my js or really overwrite SearchIndex inside typeahead.js

Thanks a lot.

Hey @rhyvl, you can't really insert it because it depends on the pull request that allows it. In other words, typeahead.js doesn't support it yet. :/

I first used the solution of @elad, but then switched to something more in the lines of what @jenswegar proposed as workaround. This is less intrusive and more maintainable. You can find it here http://stackoverflow.com/questions/22059933/twitter-typeahead-js-how-to-return-all-matched-elements-within-a-string

I'm happy you found something that works for you, and you're right about a solution that doesn't require modifications to the distribution being less intrusive and more maintainable. Still, I think it's a workaround for the fact that typeahead.js stores data using a Trie and doesn't provide a way to instrument storage/searches. But as @jharding pointed out, this may be a design choice because typeahead.js isn't meant to be more than a simple string autocomplete engine, in which case, c'est la vie. :)

If anyone finds there way here...I ended up adding more tokens in the datumTokenizer function. Note that I am using lodash to trim empty space, you'll need to provide whatever trim function you are using here if you aren't using lodash or underscore.

'datum.name' is contrived. If you are providing a data source of objects, you'll want to use whatever field is appropriate here. If it is just a string, then you should just use 'datum'

datumTokenizer: function(datum) {
  var name = datum.name;
  var tokens = [name].concat(Bloodhound.tokenizers.nonword(name));

  for(var i = 1, l = name.length; i < l; i++) {
    var char = name.charAt(i);

    if(_.trim(char) !== ''
      && char === char.toUpperCase()) {
      tokens.push(name.slice(i));
    }
  }

  return tokens;
}

@sturdynut
This repo seems abandoned.. some kind souls continue maintaing it here: https://github.com/corejavascript/typeahead.js
I'd say the useful thing to do is sending pull request there.

@namtab00 - Thanks! Good to know...:)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Neeeeena picture Neeeeena  路  3Comments

GreatPotato picture GreatPotato  路  8Comments

GunnarLieb picture GunnarLieb  路  4Comments

GiovanniMounir picture GiovanniMounir  路  5Comments

digitalpenguin picture digitalpenguin  路  4Comments