I’ve been looking into Knockout JS recently and wanted to see how it could be integrated with JQuery (and JQuery.UI) to have an autocomplete field.
Some of the examples I found were doing what I wanted, but too complicated for me to understand with my limited JavaScript experience or were just not very generic at all.
I also wanted to still have the original object supplying the label after a selection was made. This can be helpful to supply values to other fields afterwards, when you simply can’t regenerate it from the label alone.
I did find one example that I managed to change to be quite “simple” and generic.
JSFiddle: Full code & working example
I’ll run you through the sourcecode step by step:
Original Data
We have a datasource that will supply the option list that will be used for the autocomplete functionality:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Currently it’s just an array containing objects with a name
and an id
property.
JQuery.UI Autocomplete widget
The original data array itself can be of any structure, but JQuery.UI’s autocomplete widget expects an array of strings as a minimum, they will be used for the label & value both, or you can supply an array of objects that have a label
and a value property
. Since we want a different value for the option’s label
and value
we will use this object array. The label
and value
properties are mandatory, but we are free to add our own properties, which we will do using the following function to convert our initial data array to a proper JQuery.UI autocomplete widget’s source array:
1 2 3 4 5 6 7 8 9 |
|
As you can see, we’ve added a source
property to hold our original object.
ViewModel
As you may know, KnockoutJS is a MVVM framework, and here is our ViewModel that will be used for the autocomplete widget:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
As you can see, it uses our previous function to convert the original data array to an options list. It also uses the KnockoutJS Observable to hold the selected value. We use the observable because we may want to know if it updates.
KnockoutJS’s observables are implementations from the Observable pattern and it will automatically let any instances that depend on the Observable know if it’s value is updated.
We use the self
property for a few reasons, it’s best explained in this stackoverflow answer. In short: it allows use to access the ViewModel from inside function scopes where this
would refer to the function being implemented instead of the parent object(the ViewModel).
KnockoutJS – Custom binding
To allow us to generically pass in the data for the Autocomplete Widget in the correct KnockoutJS manner, we will implement a custom binding.
View binding
I think it’s easier to understand it’s functionality when you see how it’s being used:
1 2 3 4 |
|
The input textbox is will be converted in the AutoComplete Widget by JavaScript code later.
The data-bind
tag is KnockoutJS’s declarative way of binding ViewModel to the View (being HTML tags).
In the data-bind
we can specify a binding handler which in this case is the autocomplete
binding handler. Built-in handlers are for example text
, which will just put the ViewModel’s value inside the bound HTML tag as text.
Our custom binding handler will be a bit more complex. It takes a parameter that is an object with 2 properties: selected
and options
. The selected
property must be a KnockoutJS Observable that will be updated with the option that was selected. The options
property will be the JQuery.UI AutoComplete’s source array with the options labels
and values
.
The properties passed are properties on the ViewModel, being selectedOption
and options
.
The binding handler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
This is a lot to take in at once. You should focus on the following:
- valueAccessor
- Represents the passed in argument, being out object containing the observable for the selected options and the options array
- updateElementValueWithLabel
- This is a function we call on each JQuery.UI Autocomplete widget’s events to set the selectedOption observable and to set the textbox to contain the label. It has the same parameters as the original widget events
- $(element).autoComplete(…)
- This is how we convert the textbox to the JQuery.UI Autocomplete widget.
- We override the default functionality in case an option is selected, the focus in the options list changes or the textbox value is changed.
- Default functionlity is to place the value of the option list in the textbox (which is strange that it doesn’t update it with the label).
Value accessor
1 2 |
|
The valueAccessor
parameter of the binding deserves some explanation. I think typically it’s not a complex object like in my case. So far I’ve seen people use multiple bindings to pass extra values to their binding handler. I don’t think it’s very clean so I just pass one object, which has multiple properties for representing all the parameters. Nothing is static typed so using this approach or the multiple binding’s is practically the same, in my opinion.
So now that we have our input, we read our individual parameters from it.
1 2 |
|
Autocomplete widget
1 2 3 4 5 6 7 8 9 10 11 12 |
|
This is pretty straigthforward, source
property takes the list of options and then we override the events on the widget.
Update Element Value With Label
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
This function will stop the default behavior of updating the textbox with the option’s value
, on the ui.item
object, we want to use its label
instead.
Finally, we update the selectedOption Observable with the whole item from the option array, containing the mandatory label
& value
properties, as well as our own object
property containing the original data item.
KnockoutJS
Don’t forget the mandatory KnockoutJS initialization code:
1 2 3 |
|
Full Code
HTML
1 2 3 4 |
|
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
|