问题
I am taking over a new website and it was using an old deprecated version of the jquery autocomplete plugin. I am trying to recreate the functionality using the latest jquery ui autocomplete and there is one feature that i can't seem to replicate.
I am trying to replicate the "mustMatch" functionality in cases where there are multiple values allows.
so basically, if i start typing any test that doesn't show up in any of the search results (even partial string search), it resets the entry for that field (instead of letting me type garbage that is not in the list of valid choices)
So lets say my list (local) is {"Bird", "Song", "Happy"}
It will let me type
Bird, Son
but if i type z after that it stays on
Bird, Son
to let me know that is an invalid entry
Is this possible to do either in jquery ui autocomplete?
I see a lot of old posts from google asking similar questions and answers like this one, but none seems to work with multiple values (and many don't seem to work at all :( )
回答1:
You could use this kind of snippet:
{ I'm using here keyup event to check, but on modern browsers, you could use input (oninput) event instead or bind onpaste event too }
http://jsfiddle.net/q2SSF/
var availableTags = [
"Bird",
"Son",
"Happy"];
function split(val) {
return val.split(/,\s*/);
}
function checkAvailable(term) {
var length = term.length,
chck = false,
term = term.toLowerCase();
for (var i = 0, z = availableTags.length; i < z; i++)
if (availableTags[i].substring(0, length).toLowerCase() === term) return true;
return false;
}
function extractLast(term) {
return split(term).pop();
}
var $autocomplete = $("#autocomplete")
// don't navigate away from the field on tab when selecting an item
.on("keydown", function (event) {
if (event.keyCode === $.ui.keyCode.TAB && $(this).data("ui-autocomplete").menu.active) {
event.preventDefault();
}
})
.on("keyup", function (event) {
var ac_value = this.value;
if (!checkAvailable(extractLast(ac_value))) this.value = ac_value.substring(0, ac_value.length - 1);
})
.autocomplete({
minLength: 0,
source: function (request, response) {
// delegate back to autocomplete, but extract the last term
response($.ui.autocomplete.filter(
availableTags, extractLast(request.term)));
},
focus: function () {
// prevent value inserted on focus
return false;
},
select: function (event, ui) {
var terms = split(this.value);
// remove the current input
terms.pop();
// add the selected item
terms.push(ui.item.value);
// add placeholder to get the comma-and-space at the end
terms.push("");
this.value = terms.join(", ");
return false;
}
});
回答2:
I mostly copied from the jQuery UI multi-select example, but made a few changes. The goal was to work exactly how you described and that this could handle any method of input: appending to the string, inserting into the string, and copying and pasting.
The two keys to modify the multi example to meet your needs, where the creation of custom filters and adding to the source method. Originally I changed the search method, but source gave me more control on how to display the choices (implement min length and keep showing after the last term was trimmed).
When the source method is executed, which seems to get fired during all manner of input types (typing, pasting, cutting), I split the inputs and check each input for validity. I check each because if somebody pasted text, then something in the middle might become invalid where it was valid before. Anything before the last term gets the exact filter applied while the last element gets the from start filter applied. The last term is also treated differently in that it is trimmed to the point the unmatched input occurs.
After that I update the input value if any changes have occurred. I then display the response to the lastTerm, taking into account the minLength value, which even the original multi example forgot to do.
I believe my solution is the best possible as it handles all methods of input and is simple in that it only adds to one function from the original example. The one downside is that there are some inefficiencies created to keep the solution simple, but these are so minor as to not cause any noticeable performance effects.
additional ideas: One other idea, would be change the split regex to /,?\s*/ so that the comma was optional. In my testing it was natural to type space after each response. Another would be to update the input value every time so the comma spacing is consistent.
jsFiddle
var availableTags = ['Bird', 'Song', 'Happy'];
function split(val) {
return val.split(/,\s*/);
}
// removes the last term from the array, and adds newValue if given
function removeLastTerm(val, newValue) {
var terms = split(val);
terms.pop();
if (newValue) {
terms.push(newValue);
}
terms.push('');
return terms.join(', ');;
}
// filter from start position from:
// http://blog.miroslavpopovic.com/jqueryui-autocomplete-filter-words-starting-with-term
function filterFromStart(array, term) {
var matcher = new RegExp('^' + $.ui.autocomplete.escapeRegex(term), 'i');
return $.grep(array, function (value) {
return matcher.test(value.label || value.value || value);
});
}
function filterExact(array, term) {
var matcher = new RegExp('^' + $.ui.autocomplete.escapeRegex(term) + '$', 'i');
return $.grep(array, function (value) {
return matcher.test(value.label || value.value || value);
});
}
$('#tags')
// don't navigate away from the field on tab when selecting an item
.bind('keydown', function (event) {
if (event.keyCode === $.ui.keyCode.TAB &&
$(this).data('ui-autocomplete').menu.active) {
event.preventDefault();
}
})
.autocomplete({
minLength: 0,
delay: 0,
source: function (request, response) {
var terms = split(request.term),
lastTrimmed = false,
lastTerm,
originalMaxIndex = terms.length - 1,
filteredMaxIndex;
if (originalMaxIndex >= 0) {
// remove any terms that don't match exactly
for (var i = originalMaxIndex - 1; i >= 0; i--) {
if (filterExact(availableTags, terms[i]).length == 0) {
terms.splice(i, 1);
}
}
filteredMaxIndex = terms.length - 1;
// trim the last term until it matches something or is emty
lastTerm = terms[filteredMaxIndex];
while (lastTerm.length != 0 &&
filterFromStart(availableTags, lastTerm).length == 0) {
lastTerm = lastTerm.substr(0, lastTerm.length - 1);
lastTrimmed = true;
}
if (lastTrimmed) {
// add modified LastTerm or reduce terms array
if (lastTerm.length == 0) {
terms.splice(filteredMaxIndex--, 1);
terms.push('');
}
else terms[filteredMaxIndex] = lastTerm;
}
if (filteredMaxIndex >= 0) {
// only execute if we've removed something
if (filteredMaxIndex < originalMaxIndex || lastTrimmed) {
this.element.val(terms.join(', '));
}
} else {
this.element.val(request.term);
}
if (this.options.minLength <= lastTerm.length) {
response(filterFromStart(availableTags, lastTerm));
}
else {
response([]);
}
}
else {
response(filterFromStart(availableTags, ''));
}
},
focus: function () {
// prevent value inserted on focus
return false;
},
select: function (event, ui) {
// add the selected value to the input.
this.value = removeLastTerm(this.value, ui.item.value);
return false;
}
});
来源:https://stackoverflow.com/questions/17133687/does-jquery-ui-autocomplete-support-restrict-typing-on-invalid-values-where-supp