How to create auto-fill combobox?

Does anyone know how to create auto-complete autocomplete with jock templates for beginners?

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 would like Knockout to play well, perhaps with jQuery autocomplete or with some JavaScript code, but not have much success.

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

+55
javascript jquery
Sep 24 '11 at 4:22
source share
9 answers

Here is the jQuery UI autocomplete binding I wrote. It is intended to mirror the binding paradigm of options , optionsText , optionsValue , value used with selection elements, with several additions (you can request parameters via AJAX, and you can distinguish what is displayed at the input to the field that appears in the appeared selection window .

You do not need to provide all the parameters. He will choose the default values ​​for you.

Here is an example without an AJAX function: http://jsfiddle.net/rniemeyer/YNCTY/

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

Here is an example with parameters obtained 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 support a version of this binding here: https://github.com/rniemeyer/knockout-jqAutocomplete

+119
Sep 24 '11 at 11:59
source share

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); } }; 

Using:

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

http://jsfiddle.net/7bRVH/214/ Compared to RP, this is a very simple, but perhaps the full scope of your needs.

+44
Apr 12 '12 at 10:00
source share

Disposal required ....

Both of these solutions are great (with Niemeyer much finer), but they both forget about recycling!

They should handle disposals by destroying jquery autocomplete (preventing memory leaks) as follows:

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

Minor improvements

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

I am using the version published by Epstone , with the following improvements:

  • Display a label (instead of a value) when you press up or down - apparently, this can be done by processing the focus event

  • Using an observable array as a data source (instead of an array)

  • Added one-time handler suggested by George

http://jsfiddle.net/PpSfR/

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

Btw, specifying minLength , since 0 allows you to display alternatives by simply moving the arrow keys without entering any text.

+4
Apr 09 '13 at 17:13
source share

I tried the Niemeyer solution with JQuery UI 1.10.x, but the autocomplete window just didn’t appear, after some searching I found a simple workaround. Adding the following rule to the end of the 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 numerical value, make sure that you change the comparison operators: from === to == and! == to! = so that type conversion is performed.

I hope this helps others

+2
May 2 '14 at 3:46
source share

Fixed error cleaning input signal for RP solution. Although this is an indirect solution, I changed this at the end of the function:

 $(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); } 
+2
Jun 02 '14 at 21:40
source share

Niemeyer's solution is great, however, I run into a problem when trying to use autocomplete inside modal. Autocomplete was destroyed in a modal private event ("Cheating error": failed to call methods on autocomplete before initialization, tried to call the "option" method). I fixed it by adding two lines to the subscription method:

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

I know this question is old, but I also looked for a very simple solution for our team using this in the form, and found out that jQuery autocomplete raises the autocompleteselect event .

It gave me this idea.

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

When the handler will simply be:

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

I liked this approach because it simplifies the handler and it does not attach jQuery events to my viewmodel. Here is a fiddle with an array instead of the url as source. This works if you click on a text field and also press Enter.

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

0
Jun 25 '15 at 21:02
source share

Another variation of the original Epstone solution.

I tried to use it, but also found that the view model is updated only when the value was entered manually. Selecting an autocomplete record left the view model with the old value, which is a bit worrying because validation still passes - it is only when viewed in the database that you see the problem!

The method I use is to intercept the jquery UI component select handler in the init init knockout bindings, which simply updates the knockout model when a value is selected. This code also includes the plumbing utility from George the helpful 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() }" /> 

Now it works very well. It is designed to work with a preloaded array of values ​​on the page, not with an api request.

0
Jul 18 '16 at 13:44
source share



All Articles