I am trying to use bootstraps typeahead as a flexible select for users. So if the item is known they select it if not a dialogue opens up allowing them to enter a new item.
I am doing this by watching for the change event on the input, if the inputs val is not in the source array (for the the typeahead) then the "add item" dialogue is shown to the user. The problem is if the user clicks one of the options a change event is sent before the typeahead has a chance to set the val. This is as the click will cause a blur on the text.
I wanted to check the active element to get around this, so in the change event look at document.activeElement and see if it is one of the typeahead options, this did not work and returned the entire body element.
Here is a trimmed down version of the code:
Html
<div id="contactModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Unknown Contact</h3>
</div>
<div class="modal-body">
<p>The contact you have entered is unknown. Press cancel to enter a different contact or fill out the details below to create a new contact</p>
<label>Name</label>
<input id="modal_contact" type="text" placeholder="dealership contact" value=""/>
<label>Email</label>
<input id="modal_contact_email" type="text" placeholder="dealership contact" value=""/>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<button id="save" class="btn btn-primary">Save changes</button>
</div>
</div>
Javascript
var contacts = ['pete','geoff'];
$('.typeahead').typeahead({source:contacts, updater : function(item){
console.log('updater fired'); //fires after first change event
return item;
}});
$('input').change(function(ev){
console.log($(ev.target).val());
if ($.inArray($(ev.target).val(), contacts) < 0)
$('#contactModal').modal();
})
And a JSFiddle version http://jsfiddle.net/k39vM/
Does any one know how I can test if the user has clicked a typeahead to find out if that caused the change event?
My previous suggestion didn't actually work because, as you mentioned, there is a somewhat unexpected blur
event triggering the change
before event the click
on the menu. I had assumed that it was related to the this.hide()
call inside the Typeahead.prototype.select
method.
However, after a bit of trial-and-error, I do think that I may have found a workaround. It's not actually the this.hide()
call within the select
method that is causing the problem. Knowing that there are two ways that the user can trigger selection helps to understand the hopefully working workaround (I only tested in Chrome): using the keyboard, such as hitting enter, or clicking on the item. As a result, knowing that the click is the problem-child of the two, I noticed that the mousedover
event is maintained when the user mouses over the dropdown.
As a result, the odd behavior can be manually ignored within a change
event. To simplify the process of determining what actually causes the next change
event I used a different (custom) event called "selected
" to denote the user has changed the value rather than a standard change
. Unfortunately, you still must manually ignore change
events when the mousedover
property is true
:
Simplified HTML (I used the p
tag because I find that Chrome has trouble with debugging JSFiddle's JavaScript, and combining with the console lead to a bad time):
<input class="typeahead " type="text" placeholder="contact" value="Peter skillet"></input>
<input type="text"></input>
<p id="text" />
JavaScript (text can be replaced by console.log
for the those interested comfortable):
var contacts = ['pete', 'geoffrey'];
var $text = $("#text");
var typeahead = $('.typeahead').typeahead({source: contacts}).data('typeahead');
typeahead.select = function () {
var val = this.$menu.find('.active').attr('data-value');
this.$element.val(this.updater(val))
.trigger('selected'); // <- unique event
return this.hide()
};
$('input.typeahead').on('change', function (e) {
var $target = $(e.target);
if (typeahead.mousedover) {
$text.html($text.html() + "mousedover [ignored selection]<br />");
}
else if ($.inArray($target.val(), contacts) < 0) {
$text.html($text.html() + "show add<br/>");
}
}).on('selected', function () {
$text.html($text.html() + "selected<br />");
});
For reference, this is the default select
method:
select: function () {
var val = this.$menu.find('.active').attr('data-value')
this.$element
.val(this.updater(val))
.change()
return this.hide()
}