【问题标题】: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 需要一个输入字段。有什么想法吗?

【问题讨论】:

    标签: javascript jquery knockout.js


    【解决方案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 发布的版本,有以下改进:

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

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

    3. 按照 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。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-08-31
                • 2020-04-03
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多