jQuery UI Autocomplete: Complete what I've typed after blur but before ajax request completes?
Let's say my autocomplete widget has 2 possible options: "JavaScript" and "jQuery". If I click type a "j" into the input, both results will appear. Now let's say I type "q" and then immediately hit tab. There is only one possible result for "jq" and that's "jQuery", but the widget selects "JavaScript" anyway because the ajax request hasn't come back yet and "JavaScript" was what was still highlighted when I hit tab.
Is there a way to "fix" this? i.e., I think what should happen is, I type "jq", hit tab, it leaves "jq" in the input until the request completes, and then when it does, picks the first option.
Still having trouble getting this to work. I tried recording when it was sea开发者_开发问答rching, and disallowing the select like Andrew does, but loading
never seems to be true, so I can't cancel it... actually, I think this is because the delay
hasn't been met and the next search hasn't even started... I'm going to have to bind something to the keypress event.
Okay. Think I've almost got it:
$('#myselector').autocomplete({
source: function(request, response) {
var data = {};
$(this.element).data('keyHandled', true).data('lastTerm', request.term);
// *snip*
$.ajax({
url: '/ajax/major_city',
dataType: 'json',
data: data,
success: function(data, textStatus, $xhr) {
response(data);
},
error: function($xhr, textStatus) {
response([]);
},
});
},
select: function(event, ui) {
if(!$(this).data('keyHandled')) {
// TODO perform another search for just one value and set the input immediately
return false;
}
this.value = ui.item.value;
$(this).trigger('change');
return false;
},
minLength: 0,
autoFocus: true,
delay: 250
})
.keydown(function(event) {
if($(this).val() !== $(this).data('lastTerm')) {
$(this).data('keyHandled', false);
}
});
This seems to properly "cancel" the select if the user tabbed out of the input after typing a bunch but before the delay was met/the next search started. Now we can perform another ajax call to grab a single "best match" value based on what's in the input and then set that. No need to pop open the search window again either.
I'm assuming you're autocomplete is configured with the autoFocus
option set to true
. With that in mind, you could do something like this:
/* Keep track of whether or not we're in an AJAX request*/
var loading = false;
$("input").autocomplete({
source: function(request, response) {
/* Beginning of an AJAX request; set loading to true */
loading = true;
$.ajax({
/* (snip) Options for your AJAX request. */
success: function(data) {
/* Loading complete. */
loading = false;
response(data);
}
});
},
autoFocus: true,
select: function () {
/* prevent the user from selecting an item if we're still loading matches */
return !loading;
}
}).bind("keydown", function(event) {
if (event.keyCode === $.ui.keyCode.TAB) {
/* Don't let the user tab out if we're still loading matches: */
return !loading;
}
});
Keep track of whether or not an AJAX call is in progress. If that's the case, don't allow the user to pick an option from the dropodown until that request has completed and the most recent matches come up.
The only part of your question that this may not satisfy is the last part (and I may be misunderstanding what you're asking for here):
[...] and then when it does, picks the first option.
This is probably possible, but from a usability standpoint it seems to make more sense to me to disallow selection of an item until the freshest items have appeared.
After all, just because one result is there for the autocomplete widget doesn't mean the user will want to select it when it finally does come up.
Probably easier to see with an example: http://jsfiddle.net/RsSTa/. Try typing "J" and then "JQ". You won't be allowed to continue with items from the stale AJAX call.
Edit 4
Made it a function.
(function($){
$.fn.ajaxselect = function(options) {
var settings = {
delay: 300,
data: function(term) {
return {term:term};
},
url: '',
select: function(item) {},
html: true,
minLength: 0,
autoSelect: true
};
if(options) {
$.extend(settings, options);
}
$(this).autocomplete({
source: function(request, response) {
var data = settings.data.call(this.element[0], request.term);
$.ajax({
url: settings.url,
dataType: 'json',
data: data,
success: function(data, textStatus, $xhr) {
response(data);
},
error: function($xhr, textStatus) {
response([]);
}
});
},
focus: function(event, ui) {
return false;
},
search: function(event, ui) {
$(this).data('lastSearch', this.value);
},
select: function(event, ui) {
if($(this).val() === $(this).data('lastSearch')) {
if(settings.select.call(this, ui.item) !== false) {
$(this).val(ui.item.value);
}
$(this).data('selectedValue',$(this).val()).trigger('change');
}
return false;
},
minLength: settings.minLength,
autoFocus: settings.autoSelect,
delay: settings.delay,
html: settings.html
}).bind('change.ajaxselect', function() {
$(this).toggleClass('selected', $(this).val() === $(this).data('selectedValue'));
});
if(settings.autoSelect) {
$(this).bind('autocompletechange', function(event, ui) {
if($(this).val() !== $(this).data('selectedValue') && this.value.length > 0) {
var self = this;
var data = $.extend({autoSelect:1},settings.data.call(this, this.value));
$(this).addClass('.ui-autocomplete-loading');
$.ajax({
url: settings.url,
dataType: 'json',
data: data,
success: function(data, textStatus, $xhr) {
if(data.length >= 1) {
var item = $.ui.autocomplete.prototype._normalize(data)[0];
if(settings.select.call(self, item) !== false) {
$(self).val(item.value);
}
$(self).data('selectedValue',$(self).val()).trigger('change');
}
},
complete: function($xhr, textStatus) {
$(self).removeClass('.ui-autocomplete-loading');
}
});
}
});
}
if(!settings.minLength) {
$(this).bind('focus.ajaxselect', function() {
if(!$(this).autocomplete('widget').is(':visible')) {
$(this).autocomplete('search','');
}
});
}
return $(this);
};
})(jQuery);
Usage:
$('#yourselector').ajaxselect({
url: '/ajax/city'
});
Works pretty well now.
精彩评论