Skip to content

Instantly share code, notes, and snippets.

@geuis
Created February 16, 2012 22:58
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 25 You must be signed in to fork a gist
  • Save geuis/1848558 to your computer and use it in GitHub Desktop.
Save geuis/1848558 to your computer and use it in GitHub Desktop.
Remote data querying for Twitter Bootstrap 2.0 Typeahead without modifications
<script>
// Charles Lawrence - Feb 16, 2012. Free to use and modify. Please attribute back to @geuis if you find this useful
// Twitter Bootstrap Typeahead doesn't support remote data querying. This is an expected feature in the future. In the meantime, others have submitted patches to the core bootstrap component that allow it.
// The following will allow remote autocompletes *without* modifying any officially released core code.
// If others find ways to improve this, please share.
var autocomplete = $('#searchinput').typeahead()
.on('keyup', function(ev){
ev.stopPropagation();
ev.preventDefault();
//filter out up/down, tab, enter, and escape keys
if( $.inArray(ev.keyCode,[40,38,9,13,27]) === -1 ){
var self = $(this);
//set typeahead source to empty
self.data('typeahead').source = [];
//active used so we aren't triggering duplicate keyup events
if( !self.data('active') && self.val().length > 0){
self.data('active', true);
//Do data request. Insert your own API logic here.
$.getJSON("http://search.twitter.com/search.json?callback=?",{
q: $(this).val()
}, function(data) {
//set this to true when your callback executes
self.data('active',true);
//Filter out your own parameters. Populate them into an array, since this is what typeahead's source requires
var arr = [],
i=data.results.length;
while(i--){
arr[i] = data.results[i].text
}
//set your results into the typehead's source
self.data('typeahead').source = arr;
//trigger keyup on the typeahead to make it search
self.trigger('keyup');
//All done, set to false to prepare for the next remote query.
self.data('active', false);
});
}
}
});
</script>
<input type="text" id="searchinput" data-provide="typeahead">
@zastava2012
Copy link

I was desperate about how to do this, finally found this page, but it's not working for me...
I call the standard bootstrap-typeahead.js first (<script src="/js/bootstrap/js/bootstrap-typeahead.js" type="text/javascript"></script>), then I paste your code below, set the getJSON's URL to a controller action ("/search?q=?", tested on browser), then after the script finally put the tag.
As I'm not seeing any error log going on, I don't know what's the problem. On Firebug or Chrome debugger, I don't see any POST action triggered when I type something into the field. All other bootstrap js are working fine. Any possible mistake I'm making above? Thanks for your help!

@geuis
Copy link
Author

geuis commented Mar 22, 2012

@zastava2012 Put up your code in a gist for us to see.

@zastava2012
Copy link

@geuis
Thanks for your reply! Here's my gist:
https://gist.github.com/2155082

Sorry for putting both view and controller code together.
Basically this is a Yii framework app, and I made a action /site/search that takes the ?q param
and makes a MySQL search, puts the results in a array that is made JSON.
The action is working fine on the browser and returns JSON properly.

Thank you very much for your help!
(sorry if I'm making any mistake as I'm a newb on gist)

@geuis
Copy link
Author

geuis commented Mar 23, 2012

You won't see POST actions. getJSON is a GET request. I don't see any obvious problems with the code. Looks like all you changed from the original is the getJSON path. Try adding a console.log(ev) right before ev.stopPropagation() and check the dev console. Want to make sure the keyup event is even firing on your input element.

Also, is the gist code running before or after the input element for the search field is rendered into the DOM?

@zastava2012
Copy link

@geuis thank you so much for your help.

Indeed, the input field was after the JS script, so I put it before,
I added the console.log(ev) and console.log($(this).val());)
and now I can see it firing in the console log in Chrome/Firebug.
Also, I think I needed to put "&callback?" at the end of my API url.

EDITED: https://gist.github.com/2155082

But the input words doesn't seem to be sent out correctly.
When I try to type "Video" (which does match a DB record), it shows like this on Chrome:

jQuery.Event
v
jQuery.Event

But I do get the correct JSON object returned:
[{"id":"8","name":"Video\u30c6\u30b9\u30c8"}](the content in 'name' contains a UTF-8 multibyte string %28JA) )

I did change the following inside your code:
while(i--){
arr[i] = data.results[i].name //changed .text to .name
}
but with no luck.

So there are 2 problems:

  • I don't know why it stops updating the "q" params after the first one fired (with a correct JSON object returned)
  • the JSON object returned is not reflected in a dynamic pull down list like in a normal typeahead.
  • I set up a regular Bootstrap typeahead form with the dummy data ("Alaska", "Alabama", etc) put as source,
    and the dynamic pull down + selection is working fine.

Again, thank you so much for your help.

@fayland
Copy link

fayland commented Mar 24, 2012

Hi

I made one with self.xhr.abort() which should work better.

https://gist.github.com/2179991

Thanks

@zastava2012
Copy link

hi fayland

thanks for your fork!
I tried that, but unfortunately it doesn't solve my problem.

@nadimattari
Copy link

@zastava2012

  • I don't know why it stops updating the "q" params after the first one fired (with a correct JSON object returned)

I've commented line 24 : self.data('active', true);

And it works ...

@zastava2012
Copy link

@nadim-parixis

thank you so much!
it seems to be working on my side too, and now I can see it firing the API every time I keydown/up.

Now I need to face the next problem: the JSON object seems to be returning (verified on Firebug),
but the pulldown candidates are not showing up.
I thought maybe my JSON object has 2 keys ('id', 'name'), and I tried with 1 key ('name') but no luck again.

UPDATED: https://gist.github.com/2155082

I tried to trace log after the function(data) in line48, but nothing shows up in console...
I know I'm almost there, I'd appreciate if you can provide me your 'last-one-mile' help!

here's a sample response when I type a keyword (i.e. "service") as I see them in Firebug/Chrome debug tool:

0: {id:5, name:What kind of new services can appear if the iPhone is equipped with NFC?}
id: "5"
name: "What kind of new services can appear if the iPhone is equipped with NFC?"
1: {id:40, name:Can social tipping platforms be really popular?}
id: "40"
name: "Can social tipping platforms be really popular?"

@burningTyger
Copy link

In case someone needs an example on how to use this script, I use it in my app: http://farhang-tyger.rhcloud.com/
The source code is here:
view: https://github.com/burningTyger/farhang-app/blob/master/views/layout.slim
the modified script: https://github.com/burningTyger/farhang-app/blob/master/public/bootstrap/js/typeahead-patch.js
the route it triggers: https://github.com/burningTyger/farhang-app/blob/master/farhang.rb#L238

Sometimes it's easier to see it in action :)

@geuis
Copy link
Author

geuis commented Mar 27, 2012

@nadim-parixis The line you commented out is kind of vital to how the entire thing works.

Notice line 45: self.trigger('keyup');

The "self.data('active', true)" lines are there to prevent duplicate keyup events from occurring. Otherwise you will get duplicate events triggering.

@geuis
Copy link
Author

geuis commented Mar 27, 2012

@zastava2012 Get rid of the 'q' in this line. The 'q' parameter is appended automatically by $.getJSON.

self.xhr = $.getJSON("/site/search?q&callback=?",{

In your controller code, I'm not sure if you're handling the parameter being passed via the 'callback'. jQuery automatically creates a callback function that wraps around the JSONP response. A sample response might look like:

jquery_1239292391239129312391({ foo:'bar' });

So what's happening is that callback=? gets translated by jQuery to "callback=jquery_1239292391239129312391". Your controller then has to wrap your JSON response in that callback function when it responds.

Now to take this further, you really only need JSONP in the case where your api endpoint can be accessed via 3rd party domains. Its simply a mechanism to bypass cross-origin security policies for XHR requests.

If your project is only running on your own domain, and you don't expect 3rd party services to use it, then just skip it.

Lets go back to the original gist that I posted. Start from scratch and just change the $.getJSON line to match this:

$.getJSON("/site/search",{
    q: $(this).val()
}, function(data) {

If you need to do a JSONP request, then simply add the callback like this and add wrapping your JSON response with the callback in your controller:

$.getJSON("/site/search?callback=?",{
    q: $(this).val()
}, function(data) {

@zastava2012
Copy link

@burningTyger
thank you for your share, I'll definitely take a look at your code!

@geuis
thanks again for your continuous help!

I followed your advice and edited my code accordingly:
UPDATED: https://gist.github.com/2155082

It gets the response JSON successfully and seems now to go beyond the function(data),
but now I have the following error in my console log.

505Uncaught TypeError: Cannot read property 'length' of undefined
(anonymous function)test:505
jQuery.Callbacks.firejquery.js:1046
jQuery.Callbacks.self.fireWithjquery.js:1164
donejquery.js:7399
jQuery.ajaxTransport.send.callbackjquery.js:8180

so that must be line36:
i=data.results.length;

do I have to name the JSON object as "results" in my controller?

@adrianhurt
Copy link

This piece of code is awesome! But now I have a problem. I'm not able to modify the option "items" to allow more than 8 items to display in the dropdown. Has anybody tried it?

Thanks!

@geuis
Copy link
Author

geuis commented Mar 28, 2012

@zastava2012 you're on the right track. You don't have to add the 'results' property to your response if you don't want to. The section there is specific to Twitter API responses. If your data is being returned as an array already, the just set it directly.

                        self.data('typeahead').source = yourData;

@geuis
Copy link
Author

geuis commented Mar 28, 2012

@adrianhurt that's interesting. I haven't looked into that yet. Maybe I'll have some time today to like through the source.

@adrianhurt
Copy link

@geuis thanks! I've tried it setting it with
self.data('typeahead').items = 15;
but nothing happens. And if I initialize the typeahead with typeahead(options) it works, but when source is refreshed the items option come back again to 8 (default).

@zastava2012
Copy link

@geuis

thank you so very much!
As you recommended, I directly put my JSON object to .source and it's working perfect.
So now I accomplished my goal thanks to your kind help.
Much gratitudes and respects...

Now, a little step further to let anyone who's interested in multibyte languages integration.
As I'm working with japanese text which involves the "transformation to chinese character" process,
the default typeahead select / focus method is quite troublesome, because when you hit return to fix the
chinese character candidate, it also puts the first source selection to the field simultaneously.
So I need now to incorporate a hack to avoid this particular issue...

peace!

@adrianhove
Copy link

This might be of no consequence, however I found a way to get the clicked item value.

Here is my fork
https://gist.github.com/2297184

@geuis
Copy link
Author

geuis commented Apr 4, 2012

@zastava2012 Did you find a solution to your double-return problem? If not, do all characters entered require the double-return conversion, or is it a mixture of some characters are single-return, and others are double-return?

If you look at the character encoding values for the double-return characters, do they exist within an explicit range? If so, you could add a check that says "if this character is in range THIS to THAT, require an extra return".

@markonsn
Copy link

Hi..Can someone help me? .. I have a form with this:

<input type="hidden" id="mid" name="mid" value="" >
<input type="text" id="full_name" name="full_name" data-provide="typeahead" >

in the javascript file:

$('#full_name').typeahead().on("keyup", function(ev) {
    ev.stopPropagation();
    ev.preventDefault();
    if ($.inArray(ev.keyCode, [40, 38, 9, 13, 27]) === -1) {
      self = $(this);
      self.data("typeahead").source = [];
      if (!self.data("active") && self.val().length > 0) {
        self.data("active", true);
         $.getJSON('/medical_records/list.json', {
          q: $(this).val()
        }, function(data) {
          var arr, i;
          self.data("active", true);
          arr = [];
          i = data.length;
          while (i--) {
            arr[i] = data[i].name;
          }
          self.data("typeahead").source = arr;
          self.trigger("keyup");
          self.data("active", false);
        });
      }
    }
  });

the JSON response is correct and is:

[{"mid":4,"name":"BILL GATES"},{"mid":12,"name":"STEVE JOBS"},{"mid":20,"name":"LINUS TORVALDS"}]

.typehead() is works fine, but I want assign the 'mid' value of JSON to 'mid' input hidden.. is posible this?

I was using jquery autocomplete() and it worked but I want to use typehead()

jQuery(function() {
    return $('#full_name').autocomplete({
      source: $('full_name').data('autocomplete-source'),
      select: function(event, ui) {
        $('#full_name).val(ui.item.name);
        $('#mid').val(ui.item.mid);
        return false;
      }
    });
  });

Thanks..

@burnz
Copy link

burnz commented Jun 15, 2012

@adrianhurt take a look at my fork...

https://gist.github.com/2935906

@adrianhurt
Copy link

@burnz, thanks a lot! It works perfectly.

@mounirmesselmeni
Copy link

@burnz Thanks a lot very helpful !

@pushpinderbagga
Copy link

thanks for the code - it works albeit it sure breaks at times. I use it as below but when correcting the query - it just doesn't work.

    .on('keyup', function(ev){

ev.stopPropagation();
ev.preventDefault();

//filter out up/down, tab, enter, and escape keys
if( $.inArray(ev.keyCode,[40,38,9,13,27]) === -1 ){

    var self = $(this);

    //set typeahead source to empty
    self.data('typeahead').source = [];

    //active used so we aren't triggering duplicate keyup events
    if( !self.data('active') && self.val().length > 0){

        self.data('active', true);

        //Do data request. Insert your own API logic here.
        $.getJSON($baseLocation+"updatejs?callback=",{
            term: $(this).val()
        }, function(data) {

            //set this to true when your callback executes
            self.data('active',true);

            //Filter out your own parameters. Populate them into an array, since this is what typeahead's source requires
            arr = [],
                i=data.length;
            while(i--){
                arr[i] = data[i].name
            }

            //set your results into the typehead's source
            self.data('typeahead').source = arr;

            //trigger keyup on the typeahead to make it search
            self.trigger('keyup');

            //All done, set to false to prepare for the next remote query.
            self.data('active', false);

        });

    }
}

});


@tcgumus
Copy link

tcgumus commented Nov 3, 2012

The function doesn't go in after
"var autocomplete = $('#searchinput').typeahead().on('keyup', function(ev){"

What did i do wrong? i downloaded the code but didnt work for me. Should i install an extra reposity?
Thank you.

@CristianDeluxe
Copy link

Thanks for your work and sorry for my English.

Here my modifications for my own requirements: You can set the url in input using "data-link", useful if you have lot of differently autocomplete sources.

    // Disable browser autocomplete
    $('.typeaheadlink').attr('autocomplete','off');

    var autocomplete = $('.typeaheadlink').typeahead()
        .on('keyup', function(ev){

            ev.stopPropagation();
            ev.preventDefault();

            //filter out up/down, tab, enter, and escape keys
            if( $.inArray(ev.keyCode,[40,38,9,13,27]) === -1 ){

                var self = $(this);

                // We get the URL from input
                var urljson = self.attr("data-link");

                //set typeahead source to empty
                self.data('typeahead').source = [];

                //active used so we aren't triggering duplicate keyup events
                if( !self.data('active') && self.val().length > 0){

                    self.data('active', true);

                    //Do data request.
                    $.ajax({
                        url: urljson,
                        type: 'get',
                        data: {q: $(this).val()},
                        dataType: 'json',
                        success: function(data) {

                            //set this to true when your callback executes
                            self.data('active',true);

                            //set your results into the typehead's source 
                            self.data('typeahead').source = data;

                            //trigger keyup on the typeahead to make it search
                            self.trigger('keyup');

                            //All done, set to false to prepare for the next remote query.
                            self.data('active', false);
                        }
                    });

                }
            }
        });
<input name="myautocompleteinput" class="typeaheadlink" data-provide="typeahead" data-items="4" data-link="http://domain.com/mydata.php" maxlength="255" type="text" id="myautocompleteinput">

Maybe this can be useful for someone.
Thanks again!
Cris

@rahulcs
Copy link

rahulcs commented Mar 21, 2013

Worked Like a charm :) Thank you :)

@hamzakc
Copy link

hamzakc commented Jul 11, 2013

Thanks for writing this up. It has helped me a lot. However I would recommend using the official twitter typeahead library and not the one that comes packaged with bootstrap.js. It allows you to do a lot more and has remote lookups built in. You can find it here

http://twitter.github.io/typeahead.js/

The api is different to the one packaged into bootstrap. I just downloaded a custom version of bootstrap and removed typeahead and started using the lib above. It seems much more flexible :)

Thanks

Hamza

@chiragdhori
Copy link

hi is there any full document available for yii bootstrap type ahead from database . i have to retrieve data from data from data base.but there is no documentation available anywhere. while i tried for that but not found anywhere..

i have to find patient name form data base but not able find that name.form that..wheater key event is working or ajax call is not working i dnt get the solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment