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

发布于 2024-12-06 09:42:05 字数 559 浏览 0 评论 0 原文

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

Does any one know the best way to create an autocomplete combobox with Knockout JS templates?

I have the following template:

<script type="text/html" id="row-template">
<tr>
...
    <td>         
        <select class="list" data-bind="options: SomeViewModelArray, 
                                        value: SelectedItem">
        </select>
    </td>
...        
<tr>
</script>

Sometimes this list is long and I'd like to have Knockout play nicely with perhaps jQuery autocomplete or some straight JavaScript code, but have had little success.

In addition, jQuery.Autocomplete requires an input field. Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

百合的盛世恋 2024-12-13 09:42:06

这是我编写的 jQuery UI 自动完成绑定。它旨在镜像与 select 元素一起使用的 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

Here is a jQuery UI Autocomplete binding that I wrote. It is intended to mirror the options, optionsText, optionsValue, value binding paradigm used with select elements with a couple of additions (you can query for options via AJAX and you can differentiate what is displayed in the input box vs. what is displayed in the selection box that pops up.

You do not need to provide all of the options. It will choose defaults for you.

Here is a sample without the AJAX functionality: http://jsfiddle.net/rniemeyer/YNCTY/

Here is the same sample with a button that makes it behave more like a combo box: http://jsfiddle.net/rniemeyer/PPsRC/

Here is a sample with the options retrieved via 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());    
    }
};

You would use it like:

<input data-bind="jqAuto: { autoFocus: true }, jqAutoSource: myPeople, jqAutoValue: mySelectedGuid, jqAutoSourceLabel: 'displayName', jqAutoSourceInputValue: 'name', jqAutoSourceValue: 'guid'" />

UPDATE: I am maintaining a version of this binding here: https://github.com/rniemeyer/knockout-jqAutocomplete

揽清风入怀 2024-12-13 09:42:06

这是我的解决方案:

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 相比,它非常基础,但也许可以满足您的需求。

Here is my solution:

ko.bindingHandlers.ko_autocomplete = {
    init: function (element, params) {
        $(element).autocomplete(params());
    },
    update: function (element, params) {
        $(element).autocomplete("option", "source", params().source);
    }
};

Usage:

<input type="text" id="name-search" data-bind="value: langName, 
ko_autocomplete: { source: getLangs(), select: addLang }"/>

http://jsfiddle.net/7bRVH/214/
Compared to RP's it is very basic but maybe fills your needs.

因为看清所以看轻 2024-12-13 09:42:06

需要处置......

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

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

init: function (element, valueAccessor, allBindingsAccessor) {  
....  
    //handle disposal (if KO removes by the template binding)
    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
        $(element).autocomplete("destroy");
    });
}

Disposal needed....

Both of those solutions are great (with Niemeyer's being much more fine grained) but they both forget the disposal handling!

They should handle disposals by destroying jquery autocomplete (prevent memory leakages) with this:

init: function (element, valueAccessor, allBindingsAccessor) {  
....  
    //handle disposal (if KO removes by the template binding)
    ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
        $(element).autocomplete("destroy");
    });
}
摘星┃星的人 2024-12-13 09:42:06

小改进,

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

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

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

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

  3. 建议添加了一次性处理程序

George http://jsfiddle.net /PpSfR/

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

顺便说一句,将 minLength 指定为 0,只需移动箭头键即可显示替代项,而无需输入任何文本。

Minor improvements,

First of all these are some very useful tips, thank you all for sharing.

I'm using the version posted by Epstone with the following improvements:

  1. Display the label (instead of the value) when pressing up or down - apparently this can be done by handling the focus event

  2. Using an observable array as the data source (instead of an array)

  3. Added the disposable handler as suggested by George

http://jsfiddle.net/PpSfR/

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

Btw, specifying minLength as 0 allows displaying the alternatives by just moving the arrow keys without having to enter any text.

原来是傀儡 2024-12-13 09:42:06

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

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

我还使用了 Knockout-3.1.0,因此我必须将 ko.dependentObservable(...) 替换为 ko.compulated(...)

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

我希望这对其他人有帮助

I tried Niemeyer's solution with JQuery UI 1.10.x, but the autocomplete box simply didn't show up, after some searching i found a simple workaround here. Adding the following rule to the end of your jquery-ui.css file fixes the problem:

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

I also used Knockout-3.1.0, so I had to replace ko.dependentObservable(...) with ko.computed(...)

In addition, if your KO View model contains some numeric value make sure you change the comparison operators: from === to == and !== to != , so that type conversion is performed.

I hope this helps others

内心激荡 2024-12-13 09:42:06

修复了 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);
}

Fixed the clearing of input on load problem for RP's Solution. Even though it's kind of an indirect solution, I changed this at the end of the function:

$(element).val(modelValue && inputValueProp !== valueProp ?
unwrap(modelValue[inputValueProp]) : modelValue.toString());

to this:

var savedValue = $(element).val();
$(element).val(modelValue && inputValueProp !== valueProp ?  unwrap(modelValue[inputValueProp]) : modelValue.toString());
if ($(element).val() == '') {
   $(element).val(savedValue);
}
聆听风音 2024-12-13 09:42:06

尼迈耶的解决方案很棒,但是当我尝试在模式中使用自动完成功能时遇到了问题。自动完成在模式关闭事件上被破坏(未捕获错误:无法在初始化之前调用自动完成的方法;尝试调用方法“选项”)我通过向绑定的订阅方法添加两行来修复它:

mappedSource.subscribe(function (newValue) {
    if (!$(element).hasClass('ui-autocomplete-input'))
         $(element).autocomplete(options);
    $(element).autocomplete("option", "source", newValue);
});

Niemeyer's solution is great, however I run into an issue when trying to use autocomplete inside a modal. Autocomplete was destroyed on modal close event (Uncaught Error: cannot call methods on autocomplete prior to initialization; attempted to call method 'option' ) I fixed it by adding two lines to the binding's subscribe method:

mappedSource.subscribe(function (newValue) {
    if (!$(element).hasClass('ui-autocomplete-input'))
         $(element).autocomplete(options);
    $(element).autocomplete("option", "source", newValue);
});
坏尐絯℡ 2024-12-13 09:42:06

我知道这个问题已经很老了,但我也在为我们的团队寻找一个非常简单的解决方案,在表单中使用它,并发现 jQuery 自动完成引发一个 'autocompleteselect' 事件

这给了我这个想法。

<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 作为源的小提琴。如果您单击文本框并且按 Enter 键,则此操作有效。

https://jsfiddle.net/fbt1772L/3/

I know this question is old, but I was also looking for a really simple solution for our team using this in a form, and found out that jQuery autocomplete raises an 'autocompleteselect' event.

This gave me this idea.

<input data-bind="value: text, valueUpdate:['blur','autocompleteselect'], jqAutocomplete: autocompleteUrl" />

With the handler simply being:

ko.bindingHandlers.jqAutocomplete = {
   update: function(element, valueAccessor) {
      var value = valueAccessor();

      $(element).autocomplete({
         source: value,
      });
   }    
}

I liked this approach because it keeps the handler simple, and it doesn't attach jQuery events into my viewmodel.
Here is a fiddle with an array instead of a url as the source. This works if you click the textbox and also if you press enter.

https://jsfiddle.net/fbt1772L/3/

素食主义者 2024-12-13 09:42:06

Epstone 原始解决方案的另一个变体。

我尝试使用它,但也发现视图模型仅在手动键入值时才会更新。选择自动完成条目会使视图模型保留旧值,这有点令人担心,因为验证仍然通过 - 只有当您查看数据库时您才会发现问题!

我使用的方法是在knockout绑定init中挂钩jquery UI组件的选择处理程序,它只是在选择值时更新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");
        });

    }
...
                    <input class="form-control" type="text" data-bind="value: ModelProperty, ko_autocomplete: { source: $root.getAutocompleteValues() }" />

现在效果很好。它旨在针对页面上预加载的值数组进行工作,而不是查询 api。

Another variation on Epstone's original solution.

I tried to use it but also found that the view model was only being updated when a value was typed manually. Selecting an autocomplete entry left the view model with the old value, which is a bit of a worry because validation still passes - it's only when you look in the database you see the problem!

The method I used is to hook the select handler of the jquery UI component in the knockout binding init, which simply updates the knockout model when a value is chosen. This code also incorporates the dispose plumbing from George's useful answer above.

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() }" />

This is now working pretty well. It is intended to work against a preloaded array of values on the page rather than querying an api.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文