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

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 自动完成绑定。它旨在反映optionsoptionsTextoptionsValuevalue 绑定范式与选择元素一起使用,并添加了几个(您可以通过 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");
        );

    
...
                    &lt;input class="form-control" type="text" data-bind="value: ModelProperty, ko_autocomplete:  source: $root.getAutocompleteValues() " /&gt;

现在运行良好。它旨在处理页面上预加载的值数组,而不是查询 api。

【讨论】:

以上是关于如何创建一个自动完成的组合框?的主要内容,如果未能解决你的问题,请参考以下文章

MVC5 创建带有自动完成功能的组合框

如何动态更改 C# 组合框或文本框中的自动完成条目?

如何在访问中禁用组合框上的自动完成功能?

创建自动完成组合框和文本框而不区分结果 C#.net

JavaFX 自动完成组合框下拉大小

如何创建自定义自动完成功能,如内置自动完成功能?