Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an auto-complete combobox?

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?

like image 695
Craig Bruce Avatar asked Sep 24 '11 04:09

Craig Bruce


People also ask

What is AutoComplete ComboBox?

The AutoComplete properties like AutoCompleteCustomSource, AutoCompleteMode and AutoCompleteSource to perform a TextBox that automatically completes user entry strings by comparing the initial letters being entered to the prefixes of all strings in a data source.

How do I create a ComboBox DropDownStyle?

Step 1: Create a combobox using the ComboBox() constructor is provided by the ComboBox class. // Creating ComboBox using ComboBox class ComboBox mybox = new ComboBox(); Step 2: After creating ComboBox, set the DropDownStyle property of the ComboBox provided by the ComboBox class.

How do I make a ComboBox editable?

In this article we will see how we can make a combo box such that user and change the value of it by typing. By default when we create a combo box we can only choose from the option in the drop down menu although in editable combo box we can set the text by our self.

How do I make my ComboBox non editable?

To make the text portion of a ComboBox non-editable, set the DropDownStyle property to "DropDownList". The ComboBox is now essentially select-only for the user. You can do this in the Visual Studio designer, or in C# like this: stateComboBox.


1 Answers

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

like image 54
RP Niemeyer Avatar answered Oct 17 '22 07:10

RP Niemeyer