有没有人知道使用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需要输入字段。有什么想法吗?
答案 0 :(得分:120)
这是我写的jQuery UI Autocomplete绑定。它旨在反映与select元素一起使用的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
答案 1 :(得分:44)
这是我的解决方案:
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相比,它非常基本但可能满足您的需求。
答案 2 :(得分:13)
需要处置......
这两种解决方案都很棒(Niemeyer的粒度更精细),但他们都忘记了处理方式!
他们应该通过破坏jquery自动完成(防止内存泄漏)处理处理:
init: function (element, valueAccessor, allBindingsAccessor) {
....
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).autocomplete("destroy");
});
}
答案 3 :(得分:4)
小改进,
首先,这些是一些非常有用的提示,谢谢大家分享。
我正在使用 Epstone 发布的版本,并进行了以下改进:
向上或向下按下时显示标签(而不是值) - 显然这可以通过处理焦点事件来完成
使用可观察数组作为数据源(而不是数组)
...
conf.focus = function (event, ui) {
$(element).val(ui.item.label);
return false;
}
...
顺便说一句,将 minLength 指定为0只允许通过移动箭头键来显示备选方案,而无需输入任何文本。
答案 4 :(得分:2)
我尝试使用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.computed(...)替换ko.dependentObservable(...)
此外,如果您的KO View模型包含一些数值,请确保更改比较运算符:从===到==和!==到!=,以便执行类型转换。
我希望这有助于其他人
答案 5 :(得分:2)
修复了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);
}
答案 6 :(得分:0)
Niemeyer的解决方案很棒,但是当我尝试在模态中使用自动完成时遇到了一个问题。自动完成在模态关闭事件中被销毁(未捕获错误:无法在初始化之前调用自动完成方法;尝试调用方法'选项')我通过在绑定的订阅方法中添加两行来修复它:
mappedSource.subscribe(function (newValue) {
if (!$(element).hasClass('ui-autocomplete-input'))
$(element).autocomplete(options);
$(element).autocomplete("option", "source", newValue);
});
答案 7 :(得分:0)
我知道这个问题已经过时了,但我也在寻找一个非常简单的解决方案,让我们的团队在表单中使用它,并发现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事件附加到我的viewmodel中。 这是一个数组而不是url作为源。如果单击文本框,并且按Enter键,则此方法有效。
答案 8 :(得分:0)
Epstone原始解决方案的另一个变体。
我尝试使用它,但也发现仅在手动输入值时才更新视图模型。选择一个自动完成条目会使视图模型留下旧值,这有点令人担心,因为验证仍然通过 - 只有当您在数据库中查看问题时才会这样做!
我使用的方法是将jquery UI组件的select处理程序挂钩到knockout绑定init中,这只是在选择值时更新knockout模型。此代码还包含了上述乔治的有用答案中的配置管道。
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");
});
}
...
&#13;
<input class="form-control" type="text" data-bind="value: ModelProperty, ko_autocomplete: { source: $root.getAutocompleteValues() }" />
&#13;
这现在运作得很好。它旨在对照页面上预加载的值数组,而不是查询api。