Typeahead v0.10.2 & Bloodhound - Working With Nested JSON Objects
Asked Answered
I

2

20

UPDATE

Based on the correct answer from @BenSmith (https://stackoverflow.com/users/203371/BenSmith) I was able to find my problem and found out I was not navigating through my JSON hierarchy properly. Here is the working code:

        // instantiate the bloodhound suggestion engine
    var engine = new Bloodhound({
        datumTokenizer: function (datum) {
            return Bloodhound.tokenizers.whitespace(datum.title);
        },
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        prefetch: {
            url: "SampleData.json",
            filter: function (data) {
                //console.log("data", data.response.songs)
                return $.map(data.response.songs, function (song) {
                    return {
                        title: song.title,
                        artistName: song.artist_name
                    };
                });
            }
        }
    });

    // initialize the bloodhound suggestion engine
    engine.initialize();

    // instantiate the typeahead UI
    $('#prefetch .typeahead').typeahead(
      {
          hint: true,
          highlight: true,
          minLength: 1
      },
      {
          name: 'engine',
          displayKey: 'title',
          source: engine.ttAdapter(),
          templates: {
              empty: [
              '<div class="empty-message">',
              'unable to find any results that match the current query',
              '</div>'
              ].join('\n'),
              suggestion: Handlebars.compile('<p><strong>{{title}}</strong> by {{artistName}}</p>')
          }
      });

Thanks @BenSmith for the help!


Original Question

I am new to working with Typeahead and Bloodhound. The documentation is not very helpful. I have a set of JSON objects that I get back from an API I am working with. I am trying to figure out how to navigate through my JSON objects so that Bloodhound can understand them.

The goal here is a user will start to type a song name. The autocomplete will then display a list of song names and the artist it was performed by.

For Example: Chimes At Midnight by Mastodon

I am using the latest versions of each library: https://twitter.github.io/typeahead.js/

The sample JSON (SampleData.json):

{"response": {"status": {"version": "4.2", "code": 0, "message": "Success"}, "songs": [{"title": "Chimes At Midnight", "artist_name": "Mastodon", "artist_foreign_ids": [{"catalog": "7digital-AU", "foreign_id": "7digital-AU:artist:29785"}, {"catalog": "7digital-UK", "foreign_id": "7digital-UK:artist:29785"}], "tracks": [], "artist_id": "ARMQHX71187B9890D3", "id": "SOKSFNN1463B7E4B1E"}, {"title": "Down Under", "artist_name": "Men at Work", "artist_foreign_ids": [{"catalog": "7digital-AU", "foreign_id": "7digital-AU:artist:50611"}], "tracks": [], "artist_id": "AR4MVC71187B9AEAB3", "id": "SORNNEB133A920BF86"}]}}

Use this site http://json.parser.online.fr/ to format the JSON easily.

The JSON I will get back will always be in the same format, but contain different data. In this example, the "tracks" are null. Other results will have data. I would also need to be able to access that data as well.

I am using the latest version of JQuery. Here is what I have included in my html page:

<script src="Scripts/jquery-2.1.1.min.js"></script>
<script src="Scripts/typeahead.bundle.min.js"></script>

Here is the HTML:

<div id="prefetch">
    <input class="typeahead" type="text" placeholder="Songs...">
</div>

Here is the script:

    // instantiate the bloodhound suggestion engine
    var engine = new Bloodhound({
        datumTokenizer: function (d) { return Bloodhound.tokenizers.whitespace(d.tokens.join(' ')); },
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        prefetch: {
            url: "SampleData.json",
            filter: function (response) {
                return response.engine;
            }
        }
    });

    // initialize the bloodhound suggestion engine
    engine.initialize();

    // instantiate the typeahead UI
    $('#prefetch .typeahead').typeahead(
      {
          hint: true,
          highlight: true,
          minLength: 1
      },
      {
          name: 'songs',
          displayKey: function (engine) {
              return engine.songs.artist_name;
          },
          source: engine.ttAdapter()
      });

When I try to run this code I get the following error:

Uncaught TypeError: Cannot read property 'tokens' of undefined

Any help or direction here would be greatly appreciated. I just need to know how to get the JSON data I am working with working with Typeahead/Bloodhound.

Thanks!

Instill answered 3/7, 2014 at 17:41 Comment(1)
Glad I could help Jared :)Variform
V
21

You need to write the filter function so that it creates an array of javascript objects to use as the datums. The following should work (I haven't tested this):

filter: function (response) {
            return $.map(response.songs, function (song) {
                return {
                    title: song.title,
                    artistName: song.artist_name
                };
            });
        }

(an example of the filter function can be found here)

And change your datumtokenizer to:

datumTokenizer: function (datum) {
        return Bloodhound.tokenizers.whitespace(datum.title);
    }

also change your displaykey to:

displayKey: 'title'

As this is the key which Typeahead will be using for searching.

As for displaying the song name and artist in the list of suggestons, I suggest you use templating (e.g. Handlebars) for displaying the results. See the "Custom Template" example in Typeahead's examples page. Your suggestion mark-up will then look similar to the following:

suggestion: Handlebars.compile('<p><strong>{{title}}</strong> by {{artistName}}</p>')
Variform answered 4/7, 2014 at 0:1 Comment(4)
Hello, thank you very much for this very well put together answer! It really helped me to understand this better. I have implemented the code and no more errors. I still have a problem though. When I try to search for a song name, my "empty" template message comes up. For some reason it is not able to find the song names when I type them. To make this simple, I have created a JSFiddle here: jsfiddle.net/VBLTS/2Instill
Forgot to mention this. I get this JS error: Uncaught TypeError: Cannot read property 'length' of undefinedInstill
I found the problem. I was not navigating through the JSON correctly. I was calling response.songs, when it really needed to be response.response.songs based on my JSON structure. So I renamed the function to data and now call data.response.songs and everything works.Instill
@user993514 Saw your comment on my phone but only now had a chance to help you out on my PC. Great news that you have got your app working and I'm glad I could help :)Variform
B
11

My answer is nothing new, but just an update for v0.11 of twitter typeahead. As always, redirect your upvotes (if any) to the original answer.

A friendly reminder to everyone who are still using Twitter-Typeahead is to move away from this repository as it is no longer maintained. Instead redirect yourself to corejavascript/typeahead.js which is a fork of the original repository, and the same author (@JakeHarding) maintains this repository.

From v0.10 to v0.11 a few things have changed, and so must the answer. Here is my new code for this newer version.

HTML

<div id="prefetch">
  <input class="typeahead" type="text" placeholder="Countries">
</div>

JS

    var tags = new Bloodhound({
        datumTokenizer: function(datum) {
            return Bloodhound.tokenizers.whitespace(datum.name);
        },
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        prefetch: {
            url: 'http://www.yourwebsite.com/js/data.json',
            cache: false,
            transform: function(response) {
                return $.map(response, function (tag) {
                    return {
                        name: tag.tagName,
                        id: tag.tagId
                    }
                });
            }
        }
    });

    $('#prefetch .typeahead').typeahead({
        minLength: 3,
        highlight: true
    }, {
        name: 'tags-dataset',
        source: tags,
        display: 'name'
    });

What has changed:

  1. There is no filter option under prefetch. Instead you have to use transform.
  2. displayKey under typeahead() function has been changed to simply display.
  3. cache under transform option is set to false so that the data source is loaded every time. This is good for testing, but in production server, this option must be disabled if caching of data is required.

As an additional note to folks who are trying this example with different data sources with cache set to true. You must always run window.localStorage.clear(); method in your console to clear the already present local storage so that new data can be retrieved. Failing to do so will result in the old data being used, and you might think why your code is not working.

Here is a working JSFIDDLE example to get you started. A few points to note:

  1. I did not know if there is a CDN for typeahead, so I simply pasted the entire typeahead javascript code in there. At the bottom of the page, you can look my function.
  2. The data source points to a link which is a file hosted in my dropbox account. In case I remove this file, this example would not work at all. Hence you need to supply your own source. Here are the contents of that file - [{"tagId":9,"tagName":"Action"},{"tagId":10,"tagName":"Adventure"},{"tagId":6,"tagName":"Books"},{"tagId":11,"tagName":"Fantasy"},{"tagId":2,"tagName":"Games"},{"tagId":1,"tagName":"Movies"},{"tagId":3,"tagName":"Music"},{"tagId":8,"tagName":"News"},{"tagId":4,"tagName":"Office"},{"tagId":5,"tagName":"Security"},{"tagId":7,"tagName":"TV-Shows"}]
Bandmaster answered 2/1, 2016 at 7:38 Comment(1)
Nice update Rash, and thanks for letting me know about corejavascript/typeahead.js !Variform

© 2022 - 2024 — McMap. All rights reserved.