如何创建一个自动完成的组合框?
Posted
技术标签:
【中文标题】如何创建一个自动完成的组合框?【英文标题】:How to create an auto-complete combobox? 【发布时间】:2011-11-24 02:51:58 【问题描述】:有人知道使用 Knockout JS 模板创建自动完成组合框的最佳方法吗?
我有以下模板:
<script type="text/html" id="row-template">
<tr>
...
<td>
<select class="list" data-bind="options: SomeViewModelArray,
value: SelectedItem">
</select>
</td>
...
<tr>
</script>
有时这个列表很长,我想让 Knockout 与 jQuery 自动完成或一些直接的 javascript 代码很好地配合,但收效甚微。
此外,jQuery.Autocomplete 需要一个输入字段。有什么想法吗?
【问题讨论】:
【参考方案1】:这是我编写的一个 jQuery UI 自动完成绑定。它旨在反映options
、optionsText
、optionsValue
、value
绑定范式与选择元素一起使用,并添加了几个(您可以通过 AJAX 查询选项,并且可以区分输入中显示的内容)框与弹出的选择框中显示的内容。
您不需要提供所有选项。它会为您选择默认值。
这是一个没有 AJAX 功能的示例:http://jsfiddle.net/rniemeyer/YNCTY/
下面是同一个示例,其中包含一个按钮,使其表现得更像一个组合框:http://jsfiddle.net/rniemeyer/PPsRC/
以下是通过 AJAX 检索的选项示例:http://jsfiddle.net/rniemeyer/MJQ6g/
//jqAuto -- main binding (should contain additional options to pass to autocomplete)
//jqAutoSource -- the array to populate with choices (needs to be an observableArray)
//jqAutoQuery -- function to return choices (if you need to return via AJAX)
//jqAutoValue -- where to write the selected value
//jqAutoSourceLabel -- the property that should be displayed in the possible choices
//jqAutoSourceInputValue -- the property that should be displayed in the input box
//jqAutoSourceValue -- the property to use for the value
ko.bindingHandlers.jqAuto =
init: function(element, valueAccessor, allBindingsAccessor, viewModel)
var options = valueAccessor() || ,
allBindings = allBindingsAccessor(),
unwrap = ko.utils.unwrapObservable,
modelValue = allBindings.jqAutoValue,
source = allBindings.jqAutoSource,
query = allBindings.jqAutoQuery,
valueProp = allBindings.jqAutoSourceValue,
inputValueProp = allBindings.jqAutoSourceInputValue || valueProp,
labelProp = allBindings.jqAutoSourceLabel || inputValueProp;
//function that is shared by both select and change event handlers
function writeValueToModel(valueToWrite)
if (ko.isWriteableObservable(modelValue))
modelValue(valueToWrite );
else //write to non-observable
if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['jqAutoValue'])
allBindings['_ko_property_writers']['jqAutoValue'](valueToWrite );
//on a selection write the proper value to the model
options.select = function(event, ui)
writeValueToModel(ui.item ? ui.item.actualValue : null);
;
//on a change, make sure that it is a valid value or clear out the model value
options.change = function(event, ui)
var currentValue = $(element).val();
var matchingItem = ko.utils.arrayFirst(unwrap(source), function(item)
return unwrap(item[inputValueProp]) === currentValue;
);
if (!matchingItem)
writeValueToModel(null);
//hold the autocomplete current response
var currentResponse = null;
//handle the choices being updated in a DO, to decouple value updates from source (options) updates
var mappedSource = ko.dependentObservable(
read: function()
mapped = ko.utils.arrayMap(unwrap(source), function(item)
var result = ;
result.label = labelProp ? unwrap(item[labelProp]) : unwrap(item).toString(); //show in pop-up choices
result.value = inputValueProp ? unwrap(item[inputValueProp]) : unwrap(item).toString(); //show in input box
result.actualValue = valueProp ? unwrap(item[valueProp]) : item; //store in model
return result;
);
return mapped;
,
write: function(newValue)
source(newValue); //update the source observableArray, so our mapped value (above) is correct
if (currentResponse)
currentResponse(mappedSource());
);
if (query)
options.source = function(request, response)
currentResponse = response;
query.call(this, request.term, mappedSource);
else
//whenever the items that make up the source are updated, make sure that autocomplete knows it
mappedSource.subscribe(function(newValue)
$(element).autocomplete("option", "source", newValue);
);
options.source = mappedSource();
ko.utils.domNodeDisposal.addDisposeCallback(element, function ()
$(element).autocomplete("destroy");
);
//initialize autocomplete
$(element).autocomplete(options);
,
update: function(element, valueAccessor, allBindingsAccessor, viewModel)
//update value based on a model change
var allBindings = allBindingsAccessor(),
unwrap = ko.utils.unwrapObservable,
modelValue = unwrap(allBindings.jqAutoValue) || '',
valueProp = allBindings.jqAutoSourceValue,
inputValueProp = allBindings.jqAutoSourceInputValue || valueProp;
//if we are writing a different property to the input than we are writing to the model, then locate the object
if (valueProp && inputValueProp !== valueProp)
var source = unwrap(allBindings.jqAutoSource) || [];
var modelValue = ko.utils.arrayFirst(source, function(item)
return unwrap(item[valueProp]) === modelValue;
) || ;
//update the element with the value that should be shown in the input
$(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());
;
你会像这样使用它:
<input data-bind="jqAuto: autoFocus: true , jqAutoSource: myPeople, jqAutoValue: mySelectedGuid, jqAutoSourceLabel: 'displayName', jqAutoSourceInputValue: 'name', jqAutoSourceValue: 'guid'" />
更新:我在这里维护这个绑定的一个版本:https://github.com/rniemeyer/knockout-jqAutocomplete
【讨论】:
用一个更像组合框的示例更新了答案:jsfiddle.net/rniemeyer/PPsRC。使用快速自定义绑定来简化将点击绑定添加到按钮。这里有一个样式更好的示例:jqueryui.com/demos/autocomplete/#combobox 更正,淘汰赛验证似乎不适用于此 遗憾的是,这些 jsFiddle 链接现在都不起作用,除非您编辑外部链接,只要它们具有内置的 knockoutjs 支持 我正在尝试使用此代码,但它不适用于 knockout.validation.js。有没有这样的例子? 我刚刚遇到了这个实现的一个小问题——该按钮只会显示自动完成列表中包含空格的项目。一种解决方法是将选项minLength: 0
添加到自动完成,然后将搜索行 (line103) 更改为 autoEl.autocomplete("search", "");
【参考方案2】:
这是我的解决方案:
ko.bindingHandlers.ko_autocomplete =
init: function (element, params)
$(element).autocomplete(params());
,
update: function (element, params)
$(element).autocomplete("option", "source", params().source);
;
用法:
<input type="text" id="name-search" data-bind="value: langName,
ko_autocomplete: source: getLangs(), select: addLang "/>
http://jsfiddle.net/7bRVH/214/ 与 RP 相比,它非常基础,但可能满足您的需求。
【讨论】:
确实,这对我有用。这是一个小提琴,每个按键都会向模拟服务器生成一个 ajax 请求:jsfiddle.net/wmaurer/WgPpq 喜欢这个,解耦自定义binder和自动补全,实现all in model,让代码可维护。谢谢。 它是否适用于 jQuery UI 1.10.3?我在这里尝试,但自动完成没有出现。 jsfiddle.net/7bRVH/327 这对我有用:优雅简洁。我无法让更复杂的版本工作。我按照其他人的建议将 addDisposeCallback() 添加到 init: 中。 我试图在从 ko.observableArray 创建的表中使用它,然后编辑字段使用自动完成。我已经能够让下拉菜单工作,但是当它调用 select 函数时,我如何在我的模型中找到该行的 observable 以将值放入?上面的数据绑定只是调用了一个看起来像的函数,实际上并没有将编辑字段绑定到一个可观察的值?【参考方案3】:需要处理....
这两种解决方案都很棒(尼迈耶的粒度要细得多),但它们都忘记了处置处理!
他们应该通过销毁 jquery 自动完成(防止内存泄漏)来处理处置:
init: function (element, valueAccessor, allBindingsAccessor)
....
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function ()
$(element).autocomplete("destroy");
);
【讨论】:
虽然有用,但这不是答案,而是评论 实际上它补充了答案,所以我想它是答案的“一部分”。尤其是当它看起来很有用时。【参考方案4】:小幅改进,
首先这些是一些非常有用的技巧,谢谢大家的分享。
我使用的是 Epstone 发布的版本,有以下改进:
向上或向下按时显示标签(而不是值) - 显然这可以通过处理焦点事件来完成
使用可观察数组作为数据源(而不是数组)
按照 George 的建议添加了一次性处理程序http://jsfiddle.net/PpSfR/
...
conf.focus = function (event, ui)
$(element).val(ui.item.label);
return false;
...
顺便说一句,将 minLength 指定为 0 允许通过移动箭头键来显示备选方案,而无需输入任何文本。
【讨论】:
【参考方案5】:我用 JQuery UI 1.10.x 尝试了Niemeyer's solution,但自动完成框根本没有出现,经过一番搜索,我找到了一个简单的解决方法here。将以下规则添加到 jquery-ui.css 文件的末尾可以解决问题:
ul.ui-autocomplete.ui-menu
z-index: 1000;
我也使用了 Knockout-3.1.0,所以我不得不将 ko.dependentObservable(...) 替换为 ko.computed(...)
此外,如果您的 KO View 模型包含一些数值,请确保更改比较运算符:从 === 到 == 和 !== 到 != ,以便执行类型转换。
我希望这对其他人有所帮助
【讨论】:
【参考方案6】:修复了 RP 解决方案在加载时清除输入的问题。尽管这是一种间接解决方案,但我在函数末尾更改了它:
$(element).val(modelValue && inputValueProp !== valueProp ?
unwrap(modelValue[inputValueProp]) : modelValue.toString());
到这里:
var savedValue = $(element).val();
$(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());
if ($(element).val() == '')
$(element).val(savedValue);
【讨论】:
【参考方案7】:Niemeyer 的解决方案很棒,但是我在尝试在模式中使用自动完成时遇到了问题。自动完成在模态关闭事件中被破坏(未捕获的错误:无法在初始化之前调用自动完成上的方法;试图调用方法 'option' )我通过在绑定的订阅方法中添加两行来修复它:
mappedSource.subscribe(function (newValue)
if (!$(element).hasClass('ui-autocomplete-input'))
$(element).autocomplete(options);
$(element).autocomplete("option", "source", newValue);
);
【讨论】:
【参考方案8】:我知道这个问题很老,但我也在为我们的团队寻找一个非常简单的解决方案,在表格中使用它,并发现jQuery autocomplete raises an 'autocompleteselect' event。
这给了我这个想法。
<input data-bind="value: text, valueUpdate:['blur','autocompleteselect'], jqAutocomplete: autocompleteUrl" />
处理程序只是:
ko.bindingHandlers.jqAutocomplete =
update: function(element, valueAccessor)
var value = valueAccessor();
$(element).autocomplete(
source: value,
);
我喜欢这种方法,因为它使处理程序保持简单,并且不会将 jQuery 事件附加到我的视图模型中。 这是一个以数组而不是 url 作为源的小提琴。如果您单击文本框并且按下回车键,这将起作用。
https://jsfiddle.net/fbt1772L/3/
【讨论】:
【参考方案9】:Epstone 原始解决方案的另一种变体。
我尝试使用它,但也发现只有在手动输入值时才会更新视图模型。选择一个自动完成条目会留下旧值的视图模型,这有点令人担心,因为验证仍然通过 - 只有当您查看数据库时才会发现问题!
我使用的方法是在knockout绑定init中hook jquery UI组件的select处理程序,它只是在选择一个值时更新knockout模型。此代码还包含上述 George 的有用答案中的 dispose 管道。
init: function (element, valueAccessor, allBindingsAccessor)
valueAccessor.select = function(event, ui)
var va = allBindingsAccessor();
va.value(ui.item.value);
$(element).autocomplete(valueAccessor);
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function ()
$(element).autocomplete("destroy");
);
...
<input class="form-control" type="text" data-bind="value: ModelProperty, ko_autocomplete: source: $root.getAutocompleteValues() " />
现在运行良好。它旨在处理页面上预加载的值数组,而不是查询 api。
【讨论】:
以上是关于如何创建一个自动完成的组合框?的主要内容,如果未能解决你的问题,请参考以下文章