我主要从jQuery UI 多选示例中复制,但做了一些更改。目标是完全按照您描述的方式工作,并且可以处理任何输入方法:附加到字符串、插入字符串以及复制和粘贴。
这两个键修改多示例以满足您的需要,其中创建自定义过滤器和添加到源方法。最初我更改了搜索方法,但源让我更多地控制如何显示选择(实现最小长度并在最后一个术语被修剪后继续显示)。
当执行源方法时,似乎在所有输入类型(键入、粘贴、剪切)中都会触发,我拆分输入并检查每个输入的有效性。我检查每一个,因为如果有人粘贴文本,那么中间的某些内容可能会在之前有效的地方变得无效。最后一个术语之前的任何内容都应用了确切的过滤器,而最后一个元素应用了从开始过滤器。最后一项的处理方式也不同,因为它被修剪到出现不匹配输入的点。
之后,如果发生任何更改,我会更新输入值。然后我显示对 lastTerm 的响应,考虑到 minLength 值,即使是原始的多示例也忘记了。
我相信我的解决方案是最好的,因为它可以处理所有输入方法,并且很简单,因为它只添加了原始示例中的一个函数。一个缺点是,为了保持解决方案的简单性而创建了一些低效率,但这些都非常小,不会导致任何明显的性能影响。
其他想法:另一个想法是将拆分正则表达式更改为 /,?\s*/ 以便逗号是可选的。在我的测试中,每次响应后输入空格是很自然的。另一种方法是每次都更新输入值,以使逗号间距保持一致。
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;
}
});