jquerytypeahead.jsbloodhound

How to set unique minLength for each datumtokenizer in typeahead dataset


Background:

I am using typeahead.js to surface search suggestions to users. I want users want to be able to see suggestions based off both the "title" & "diff" of my dataset. The issue I'm having is that when the title & diff fields overlap, it can lead to some confusing results.

Typeahead Results

In this image you can see that active query is "Ea" and typehead is surfacing suggestions for both the title "Earthbound" & the diff "Easy". I do not like this result, I would instead like a queries with less than 3 characters to only pull from the "title" field.

Question:

How can I set a unique minLength for each datumtokenizer in my dataset? So that suggestions pull from titles starting on the first character and suggestions don't start pulling from the "diff" until the query has at least 3 characters.

Code:

var games = new Bloodhound({
  datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title', 'diff'),
  queryTokenizer: Bloodhound.tokenizers.whitespace,
  prefetch: 'https://api.myjson.com/bins/ss93y'
);

$('.basic .typeahead').typeahead({
  hint: false,
  highlight: true,
  minLength: 1,
  limit: 5,
}, {
  name: 'games',
  display: 'title',
  source: games,
  templates: {
    header: '<h3 class="games-header">Games</h3>'
  }
});

JS Fiddle: https://jsfiddle.net/n3my5r8u/


Solution

  • Given the level of customization you needed, I would recommend implementing your own data fetching and substringMatcher function, instead of using Bloodhound. I based my solution on the first example from official documentation

    fetch('https://api.myjson.com/bins/ss93y').then(result => {
      return result.json();
    }).then(games => {
      var substringMatcher = (query, cb) => { 
        var normalizedQuery = query.toLowerCase();
        if (normalizedQuery.length < 3) {
          /* if query shorter than 3 chars, filter on title only */
          return cb(games.filter(game => game.title.toLowerCase().includes(normalizedQuery)));
        } else {
          /* if query at least 3 chars, filter on title or diff */
          return cb(games.filter(game => game.title.toLowerCase().includes(normalizedQuery) || game.diff.toLowerCase().includes(normalizedQuery)));
        }
      }
    
      $('.basic .typeahead').typeahead({
        hint: false,
        highlight: true,
        minLength: 1,
        limit: 50,
      }, {
        name: 'games',
        display: 'title',
        source: substringMatcher,
        templates: {
          header: '<h3 class="games-header">Games</h3>'
        }
      });
    });
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://twitter.github.io/typeahead.js/releases/latest/typeahead.bundle.js"></script>
    
    <div class="basic">
      <input class="typeahead" type="text" placeholder="Search">
    </div>

    Note that I converted both the query and the game data-set to lower case so your search will be case insensitive.