Javascript – Bootstrap Typeahead with AJAX source: not returning array as expected

ajaxbootstrap-typeaheadjavascriptjquerytwitter-bootstrap

I am having a lot of trouble getting bootstraps typeahead to work properly with an AJAX source.

If I alert out the array that is being returned it is perfectly fine, even when I hard code it to test it. It seems to return nothing most of the time or only a few items from the array when you have typed in a near match.

Here is what I have at the moment:

$('#companyNameInput').typeahead({
      source: function(query, process){

          $.ajax({
            url: ROOT+'Record/checkCompanyName',
            async: false,
            data: 'q='+query,
            type: 'POST',
            cache: false,
            success: function(data)
            {
              companiesFinal = [];
              map = {};
              companies = $.parseJSON(data);
              $.each(companies, function(i, v){
                map[v.name] = v.id;
                companiesFinal.push(v.name);
              })
            }
          })
          process(companiesFinal);

          // return ['test1', 'test2'] This works fine
          return companiesFinal;
      }

Does anyone have an idea why this is working properly?


Here is an example of the object array returned from my PHP script. Objects with IDs 1 and 1216 show up on the typeahead dropdown, but non of the others do. I can not see any patterns or clue as to why only these would show and not the others.

[
   {
      "id": "1265",
      "score": "40",
      "name": "LMV AB"
   },
   {
      "id": "10834",
      "score": "33",
      "name": "Letona"
   },
   {
      "id": "19401",
      "score": "33",
      "name": "Lewmar"
   },
   {
      "id": "7158",
      "score": "33",
      "name": "Lazersan"
   },
   {
      "id": "3364",
      "score": "33",
      "name": "Linpac"
   },
   {
      "id": "1216",
      "score": "33",
      "name": "L H Evans Limted"
   },
   {
      "id": "1",
      "score": "33",
      "name": "LH Evans Ltd"
   },
   {
      "id": "7157",
      "score": "33",
      "name": "Lazersan"
   }
]

And finally the array that is past in process(companiesFinal):

["LMV AB", "Letona", "Lewmar", "Lazersan", "Linpac", "L H Evans Limted", "LH Evans Ltd", "Lazersan"]

Anyone have any clue? I am still totally clueless as to why this isn't working still 🙁


$('#companyNameInput').typeahead({
      source: function(query, process){

        companyTOut = setTimeout(function(){
          return $.ajax({
            url: ROOT+'Record/checkCompanyName',
            data: 'q='+query,
            type: 'POST',
            dataType: 'json',
            cache: false,
            success: function(data)
            {
              var count = 0;
              var companiesFinal = [];
              map = [];
              $.each(data, function(i, v){
                map[v.name] = [v.id, v.score];
                companiesFinal.push(v.name);
              })
              process(companiesFinal);

            }
          })
        }, 250)
      },
      minLength: 2,
      highlighter: function(item)
      {
        $('#companyNameInput').closest('.control-group').removeClass('success')
        companyLocked = false;
        return '<span class="unselectable" title="'+map[item].score+'">'+item+'</span>';
      },
      updater: function(item)
      {
        selectedEntityId = map[item][0];
        selectedCountryScore = map[item][1];
        lockCompany(selectedEntityId);
        return item;
      }
    });

$output .= '['.$output;
    foreach($results as $result) {
      $output .= '{"id":"'.$result['id'].'",';
      $output .= '"score":"'.$result['score'].'",';
      $output .= '"name":'.json_encode($result['name']).'},';
    }
    header('Content-Type: application/json');
    echo substr($output, 0, strlen($output)-1).']';

Console output for "parm":

[Object, Object, Object, Object, Object, Object]
0: Object
id: "25024"
name: "part"
score: "75"
__proto__: Object
1: Object
id: "15693"
name: "pari"
score: "75"
__proto__: Object
2: Object
id: "28079"
name: "Pato"
score: "50"
__proto__: Object
3: Object
id: "18001"
name: "PASS"
score: "50"
__proto__: Object
4: Object
id: "15095"
name: "PSR"
score: "33"
__proto__: Object
5: Object
id: "22662"
name: "PRP"
score: "33"
__proto__: Object
length: 6
__proto__: Array[0]

Best Answer

Update 2

Ah, your service returns items that actually don't match the query parm. Your typeahead query is 'parm', with which none of the returned results match. You can override the matcher function used by the typeahead plugin, see the docs. Simply implement it as return true to match all results returned by your service.

Update 1

This is an updated version, that maps the name to an id, which can be used later. A jsfiddle is available.

var nameIdMap = {};

$('#lookup').typeahead({
    source: function (query, process) {
        return $.ajax({
            dataType: "json",
            url: lookupUrl,
            data: getAjaxRequestData(),
            type: 'POST',
            success: function (json) {
                process(getOptionsFromJson(json));
            }
        });
    },
    minLength: 1,
    updater: function (item) {
        console.log('selected id'+nameIdMap[item]);
        return item;
    }
});

function getOptionsFromJson(json) {
    $.each(json, function (i, v) {
        nameIdMap[v.name] = v.id;
    });

    return $.map(json, function (n, i) {
        return n.name;
    });
}

Original answer

You need to make the call async and call the process callback from within the success callback like this:

$('#companyNameInput').typeahead({
    source: function (query, process) {
        $.ajax({
            url: ROOT + 'Record/checkCompanyName',
            // async: false, // better go async
            data: 'q=' + query,
            type: 'POST',
            cache: false,
            success: function (data) {
                var companiesFinal = ... // snip
                process(companiesFinal);
            }
        })
    }
});

The return ['test1', 'test2']; works, because the source function is then basically set to:

// do ajax stuff, but do nothing with the result
// return the typeahead array, which the typeahead will accept as the result:
return ['test1', 'test2'];

Notes

There is a one-liner to fill companiesData:

var companiesFinal = return $.map(data, function (n, i) { n.name; });

And you probably want to declare your variables using var; otherwise they'll have global scope, which will bite you.