Typeahead.js: event to tell when user typed their own value vs selecting a suggestion

Created on 28 May 2013  ·  23Comments  ·  Source: twitter/typeahead.js

Is there a way to get an event when the user "selects" the text that they have typed out, which is separate from the offered suggestions? I.e. user has typed 'apple', with provided suggestion 'applesauce'; however user really wants to submit simply 'apple'.

Originally I tried using the 'change' event for this:

    }).on('typeahead:selected', function($e, datum) {  // suggestion selected
          console.log('selected: ' + datum['url']);
          window.location = datum['url'];
    }).on('change', function(e) {                     // user typed their own value
          value = $('input.search-input').val();
          window.location = '/search_path?search=' + encodeURIComponent(value);
    });

but unfortunately 'change' also seems to fire when the user selects from the drop-down, so if the user types 'apple' and then clicks 'applesauce' there's a kind of race condition where sometimes the form submits 'apple' and sometimes 'applesauce', depending on the speed of the remote.

select

Most helpful comment

I needed that event too, to display a warning when the user types a "custom" value.
It feels pretty hacky looking into "private" data, but here's my temporary solution:

$('#my-item').on('change', function() {
  var $input = $(this);
  var available = $input.data('ttView').datasets[0].itemHash;
  var datum = _.findWhere(available, {value: $(this).val()});
  if (datum) {
    $input.trigger('typeahead:selected', datum);
  } else {
    $input.trigger('typeahead:uservalue', $(this).val());
  }
});

which I then listen to as:

$('#my-item').on('typeahead:selected',  function(e, datum) { console.log('existing: ' + datum.value); });
$('#my-item').on('typeahead:uservalue', function(e, value) { console.log('new: ' + value); });

This works both when the users selects an existing item, or manually types it out.

All 23 comments

This is exactly what I'm looking for as well. I want the ability to just submit the form with the inputted text just like a normal search box or exactly the way google works. The plugin has all of the event handlers so encapsulated that I can't gain access to override and provide the ability to just submit the form.

Maybe I'm missing something, but why can't you just listen for the submit event? Does the typeahead not belong to a form?

Perhaps I'm the one who was missing something. :-) Simply submitting the form when the user hits 'enter' is a way to differentiate from the suggestion selection. At least for me this works since I send the user elsewhere via window.location = when typeahead:selected fires.

I needed to add the following to get my form to submit on enter:

    $("input.search-input").keypress(function (event) {
        if (event.which == 13) {
            $("#search-form").submit();
        }
    });

Getting an event would be nice, but this does solve the problem for me.

Whoops sorry wrong thread. I tried the keypress solution but then you can't use the enter button when you use the down arrows and select an item. That's the issue that I was worried about which is overriding some other keystroke event that the app was waiting on like selecting one of the typeahead items.

The "change" event; however, does seem to be working for me. I've done about 50 test in my console and I can't make it "double submit" on my site. I was trying to independently verify for everybody.

Actually I can now confirm that I've seen this behavior on some of my testers computers. I resolved like this:

example:

myTypeahead.on('change',function(evt,data){
if (!data) {
window.location = '/Search.php?search=' + encodeURIComponent($('.typeahead').val());
}
return false;
});

@stormflurry the enter button works fine for me when navigating via arrow keys and selecting a suggestion. Tested w/ Safari, Chrome, Android and iOS. I'm using .on('typeahead:selected') for that though, so maybe the problem is that you're using change ?

Yes using both change and selected as follows

// Attach initialized event to it
myTypeahead.on('typeahead:selected',function(evt,data){
    myTypeahead.on('change').unbind();
    window.location = data.url;
    return false;
    });

// Attach initialized event to it
myTypeahead.on('change',function(evt,data){
    if (!data) {
        window.location = '/Search.php?search=' + encodeURIComponent($('.typeahead').val());
        }
    return false;
    }); 

I'd get rid of the 'change' handler and make that the default form behavior- have your form submit to /Search.php, and let the typeahead input supply the value for 'search' naturally. Then your typeahead:selected handler will take care of the case when a suggestion is selected.

I needed to add the following to get my form to submit on enter:

Check out this comment by me from another issue, it may be useful.

As I've played with this more and more I'm changing this into a bug.

In IE9 and below the enter key does not either submit the form or navigate to a selected item. A mouse click works perfectly and ironically enough a "tab" key on a selected item navigates to the selected item Url in IE7, 8, & 9.

So in reality the solution is rather complex and goes in two separate directions:

1) We still need a solution to reliably "submit" the form if the user has not selected an item. You cannot just tie an "enter key" event to submit the form as it will override the work that's been done to force different behaviors on the enter key example use the down error and press enter and it submits the form doesn't fire the ":selected" event.

2) There is a bug in

My jQuery version is 1.7.1.
My typeahead version is 0.9.2

I've also applied the fix on #188 but that fix is not ideal because there is a momentary flash of the user input rather than the selection as the window closes so I need to update that thread as well.

Since my initialization is compressed here is the code:

// initialize typeahead on a element having id #myTypeahead
var myTypeahead = $('.typeahead').typeahead({
    name: 'parts'
    , limit: 21
    , remote: {
        url: '/ajax.php?selectType=TypeAhead&searchText=%QUERY'
        }
    , template: '<p>{{value}}</p>'
    , engine: Hogan
    });         

// Attach initialized event to it
myTypeahead.on('typeahead:selected',function(evt,data){
    myTypeahead.on('change').unbind();
    window.location = data.url;
    return false;
    });

// Attach initialized event to it
myTypeahead.on('change',function(evt,data){
    if (!data) {
        window.location = '/Parts/PartResult.php?search=' + encodeURIComponent($('.typeahead').val());
        }
    return false;
    }); 

Let me know if you need to see more information.

You can now see my live implementation at www.eeuroparts.com. Sorry for the shameless plug but I figured it would help to provide more info.

Thanks for the thorough comment @stormflurry. Looks like this is definitely something that needs to be simplified.

This would be a very helpful event. We are creating a form that has multiple typeahead input fields, and would like to be sure the user selects valid entries for all the inputs prior to allowing a submit. We would also like to show a message (client-side) indicating that an invalid entry was made, again ... prior to allowing a submit.

I needed that event too, to display a warning when the user types a "custom" value.
It feels pretty hacky looking into "private" data, but here's my temporary solution:

$('#my-item').on('change', function() {
  var $input = $(this);
  var available = $input.data('ttView').datasets[0].itemHash;
  var datum = _.findWhere(available, {value: $(this).val()});
  if (datum) {
    $input.trigger('typeahead:selected', datum);
  } else {
    $input.trigger('typeahead:uservalue', $(this).val());
  }
});

which I then listen to as:

$('#my-item').on('typeahead:selected',  function(e, datum) { console.log('existing: ' + datum.value); });
$('#my-item').on('typeahead:uservalue', function(e, value) { console.log('new: ' + value); });

This works both when the users selects an existing item, or manually types it out.

I am looking for something similar. In my case, I send an ajax post on selected and on autocompleted, but I need another event for when the typeahead has a user entered value and the user left the typeahead input.

My scenario was a bit different, so if you're reading this comment and expecting a solution to the issue raised by OP, you may stop right here.

While this do not intend to solve the issue raised by OP, it might help others in similiar scenarios to what I'll describe.

Scenario:

There's a form with city and district fields. They're "chained" in a way that if the user does not select/autocomplete a valid city, the district field should be automatically emptied and disabled. If he choses a valid city, then the district field should be re-enabled, focused, and its suggestions must be related only to the selected city.

Thought: It would be great to have built-in events to enable us to handle these (edge-)cases.

Solution:

The following is the skeleton I came up with to make this work. I trigger 2 custom events (typeahead:_changed, and typeahead:_done - feel free to come up with better names) to control the required states.

$('input#example').on('typeahead:selected', function(object, datum) {
    console.log('typeahead:selected');
    $(this).trigger('typeahead:_done', [object, datum]);
}).on('typeahead:autocompleted', function(object, datum) {
    console.log('typeahead:autocompleted');
    $(this).trigger('typeahead:_done', [object, datum]);
}).on('change', function() {
    $(this).trigger('typeahead:_changed');
}).on('typeahead:_changed', function() {
    console.log('typeahead:_changed');
    // This event is always triggered before 'typeahead:_done', so you have a
    // chance to set a variable/property to indicate the value is dirty.
}).on('typeahead:_done', function(evt, object, datum) {
    console.log('typeahead:_done');
    // This event is triggered ONLY when the user selects or autocompletes
    // with an entry from the suggestions.
});

It looks a bit ugly, but it works. Please, let me know if you have a better solution.

Thanks @rprieto, your solution has helped me a lot!

I needed this and had this solution: https://github.com/twitter/typeahead.js/pull/560

Hi guys,

It's simple. The custom events are fired in the document scope, so;

to get ​​on select, try this:
$ (document) on ('typeahead: selected.', function (event, dataset_selected_Item, dataset_name) {
console.log (dataset_selected_Item.url);
console.log (dataset_selected_Item.value);
});

to get ​​on ​​at the end try this:
$ (document) on ('typeahead: autocompleted.', function (event, dataset_selected_Item, dataset_name) {
console.log (dataset_selected_Item.url);
console.log (dataset_selected_Item.value);
});

Hope this helps

Need solutions #560

Somewhere along the line, change stopped getting fired when a selection happened. Anyways, in v0.11 the event system is getting overhauled to add more flexibility. The 2 relevant events for this issue will be typeahead:select and typeahead:change. typeahead:select will be triggered when the end-user selects a suggestion. typeahead:change simulates the native change event, but normalizes it for typeahead.js. Basically, when the typeahead input loses focus and the query has changed since it originally gained focus, typeahead:change will get triggered.

With that said, this issue should be solved in v0.11 so I'm going to go ahead and close it. Stay tuned for documentation updates to learn more about all of the coming changes.

I'm using v 11.1 (most recent as of this writing) and seeing the same kinds of things previous posters have complained about. Contrary to what jharding wrote on Aug 15, 2014, I don't think v0.11 has solved this and the issues should be reopened.

Thank you @rprieto .. Your solution works ..

@rprieto $input.data('ttView') is undefined, how do i define datum now?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

digitalpenguin picture digitalpenguin  ·  4Comments

GiovanniMounir picture GiovanniMounir  ·  5Comments

agborkowski picture agborkowski  ·  5Comments

Neeeeena picture Neeeeena  ·  3Comments

jimmynotjim picture jimmynotjim  ·  8Comments