如何创建自动完成组合框?

IT技术 javascript jquery knockout.js
2021-02-04 23:39:28

有谁知道使用 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 需要一个输入字段。有任何想法吗?

6个回答

这是我编写的 jQuery UI 自动完成绑定。它旨在通过一些添加来反映与 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

更正,淘汰赛验证似乎不适用于此
2021-03-20 23:39:28
使用更像是组合框的示例更新了答案:jsfiddle.net/rniemeyer/PPsRC使用快速自定义绑定来简化将点击绑定添加到按钮的过程。具有更好样式的示例在这里:jqueryui.com/demos/autocomplete/#combobox
2021-03-21 23:39:28
遗憾的是,这些 jsFiddle 链接现在都不起作用,除非您编辑外部链接,除非它们具有内置的 Knockoutjs 支持
2021-03-23 23:39:28
我刚刚遇到了这个实现的一个小问题 - 该按钮只会显示自动完成列表中包含空格的项目。解决方法是将选项添加minLength: 0到自动完成,然后将搜索行 (line103) 更改为autoEl.autocomplete("search", "");
2021-03-28 23:39:28
我正在尝试使用此代码,但它不适用于 Knockout.validation.js。有这样的例子吗?
2021-04-09 23:39:28

这是我的解决方案:

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
2021-03-14 23:39:28
这对我有用:优雅的简洁。我无法让更复杂的版本工作。我按照其他人的建议将 addDisposeCallback() 添加到 init: 中。
2021-03-15 23:39:28
喜欢这个,解耦自定义绑定器和自动完成,并在模型中实现所有,使代码可维护。谢谢。
2021-03-21 23:39:28
它适用于 jQuery UI 1.10.3 吗?我在这里尝试,但自动完成没有出现。jsfiddle.net/7bRVH/327
2021-03-30 23:39:28
我试图在从 ko.observableArray 创建的表中使用它,然后编辑字段使用自动完成。我已经能够让下拉菜单正常工作,但是当它调用 select 函数时,如何在我的模型中找到该行的 observable 以将值放入?上面的数据绑定只是调用了一个函数,实际上并没有将编辑字段绑定到一个可观察值?
2021-04-06 23:39:28

需要处理....

这两种解决方案都很棒(尼迈耶的粒度更细),但它们都忘记了处理!

他们应该通过以下方式销毁 jquery 自动完成(防止内存泄漏)来处理处置:

init: function (element, valueAccessor, allBindingsAccessor) {  
....  
    //handle disposal (if KO removes by the template binding)
    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
        $(element).autocomplete("destroy");
    });
}
实际上它补充了答案,所以我想它是答案的“一部分”。尤其是当它看起来很有用的时候。
2021-03-15 23:39:28
虽然有用,但这不是答案而是评论
2021-03-21 23:39:28

小改进,

首先这些是一些非常有用的技巧,谢谢大家的分享。

我正在使用Epstone发布的版本,进行了以下改进:

  1. 向上或向下按时显示标签(而不是值) - 显然这可以通过处理焦点事件来完成

  2. 使用可观察数组作为数据源(而不是数组)

  3. 按照George 的建议添加了一次性处理程序

http://jsfiddle.net/PpSfR/

...
conf.focus = function (event, ui) {
  $(element).val(ui.item.label);
  return false;
}
...

顺便说一句,将minLength指定为 0 允许仅通过移动箭头键而无需输入任何文本来显示备选方案。

在 JQuery UI 1.10.x 上尝试了Niemeyer 的解决方案,但自动完成框根本没有出现,经过一番搜索,我在这里找到了一个简单的解决方法将以下规则添加到 jquery-ui.css 文件的末尾可以解决问题:

ul.ui-autocomplete.ui-menu {
  z-index: 1000;
}

我还使用了 Knockout-3.1.0,所以我不得不用 ko.computed(...) 替换 ko.dependentObservable(...)

此外,如果您的 KO 视图模型包含一些数值,请确保更改比较运算符:从 === 到 == 和 !== 到 != ,以便执行类型转换。

我希望这对其他人有帮助