Using the jQuery UI AutoComplete widget with Knockout

The jQuery UI AutoComplete widget is a nice one to use when the a set of know values is frequently used, like a list of city names, but you still want to leave the end user the capability to enter completely new values. And as can be seen from the examples it’s basic usage is really simple and straightforward, something like:

   1: var firstNames = ['Joe', 'Maurice', 'Jake'];


   2: $('#firstName').autocomplete({ source: firstNames });

 

Adding Knockout to the mix

Knockout is also a nice data-binding library that is really easy to use. A simple page that lets a user enter names and shows the in a list might look something like this:

   1: <!DOCTYPE html>


   2: <html xmlns="http://www.w3.org/1999/xhtml">


   3: <head>


   4:     <title>KnockoutJS</title>


   5:     <link href="/Content/themes/base/jquery-ui.css" rel="stylesheet" />


   6: </head>


   7: <body>


   8:     <input id="firstName" type="text" data-bind="value: firstName" />


   9:     <button data-bind="click:addPerson">Add</button>


  10:     <br />


  11:     Current person: <label data-bind="text:firstName"></label>


  12:  


  13:     <ul data-bind="foreach: people">


  14:         <li data-bind="text:firstName"></li>


  15:     </ul>


  16:  


  17:     <script src="/Scripts/knockout-3.0.0.js"></script>
   1:  
   2:     <script src="app.js">
</script>


  18: </body>


  19: </html>

 

With the following associated script:

   1: (function () {


   2:     'use strict';


   3:     


   4:     var vm = {


   5:         firstName: ko.observable(),


   6:         people: ko.observableArray(),


   7:         addPerson: function () {


   8:             vm.people.push({ firstName: vm.firstName() });


   9:             vm.firstName('');


  10:         }


  11:     };


  12:  


  13:     ko.applyBindings(vm);


  14: }());


  15:  

 

Simple enough :-)

 

Combining jQuery UI AutoComplete with Knockout

Unfortunately combining the two isn’t quite as easy. :-(

Adding the required jQuery and jQuery UI script files as well as the required CSS and then adding the first two lines to activate the AutoComplete widget to our JavaScript doesn’t quite produce the hoped for result. We can still type a name in the input field and add it, no change there. However if we pick a value from the suggested names from the AutoComplete widget it appears in the input field but a blank value is added to the list when we click the Add button.

   1: (function () {


   2:     'use strict';


   3:     


   4:     var firstNames = ['Joe', 'Maurice', 'Jake'];


   5:     $('#firstName').autocomplete({ source: firstNames });


   6:  


   7:     var vm = {


   8:         firstName: ko.observable(),


   9:         people: ko.observableArray(),


  10:         addPerson: function () {


  11:             vm.people.push({ firstName: vm.firstName() });


  12:             vm.firstName('');


  13:         }


  14:     };


  15:  


  16:     ko.applyBindings(vm);


  17: }());


  18:  

 

The solution

The reason that the value selected from the AutoComplete dropdown is never picked up by the Knockout data binding and propagated to the view model is that of events. Knockout uses the HTML change event to detect is the value has changed which fires when a control loses the input focus and its value has been modified since gaining focus. As the AutoComplete fill the control when it has no focus there is no change event.

The fix is simple, just use the blur event instead.

So with a small change to the Knockout binding an specifying that the valueUpdate should be done on blur everything works as expected.

The "fixed" markup:

   1: <!DOCTYPE html>


   2: <html xmlns="http://www.w3.org/1999/xhtml">


   3: <head>


   4:     <title>KO AutoComplete</title>


   5:     <link href="/Content/themes/base/jquery-ui.css" rel="stylesheet" />


   6: </head>


   7: <body>


   8:     <input id="firstName" type="text" data-bind="value: firstName, valueUpdate: ['blur']" />


   9:     <button data-bind="click:addPerson">Add</button>


  10:     <br />


  11:     Current person: <label data-bind="text:firstName"></label>


  12:  


  13:     <ul data-bind="foreach: people">


  14:         <li data-bind="text:firstName"></li>


  15:     </ul>


  16:  


  17:     <script src="/Scripts/knockout-3.0.0.js"></script>
   1:  
   2:     <script src="/Scripts/jquery-1.10.2.js">
   1: </script>
   2:     <script src="/Scripts/jquery-ui-1.10.3.js">
   1: </script>
   2:     <script src="app.js">
</script>


  18: </body>


  19: </html>



 



Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>