Knockout with JQuery UI Autocomplete (Resurrected)

6,548 views
Skip to first unread message

Stacey Thornton

unread,
Jul 21, 2011, 11:38:14 AM7/21/11
to KnockoutJS
I know this has been asked before, but a lot of the threads have kind
of been pushed away and the answers are pretty disparate and I'm not
able to find any kind of conclusion.

Has anyone to date managed a working situation of Knockout using
jQuery UI autocomplete where the label and value are separate
entities? (i.e. the 'Text' is one thing, but the 'data' is, for
instance, an integer or guid that is actually its ID under the hood)

david.s...@gmail.com

unread,
Jul 21, 2011, 5:24:46 PM7/21/11
to knock...@googlegroups.com
Taking this completely outside of knockout.
in your model you would do a <input type="hidden" data-bind="value: guid "><input data-bind="value: label" class="completeme">

$('.container').delegate('.completeme', 'focus', function(){
     $(this)
        .removeClass('completeme') // Get rid of the completeme so that we dont rebind autocomplete again on next focus.
       .autocomplete({
          //Standard options for autocomplete would go here. for example http://jqueryui.com/demos/autocomplete/#custom-data
     })
})

rpn

unread,
Jul 22, 2011, 12:01:38 AM7/22/11
to knock...@googlegroups.com
I was playing around with this a little bit trying to do something similar to the options binding.   


I originally was playing with selecting an option and having it set a value to a property of your choice (or the item) that was different than the text displayed in the field.  This didn't feel quite right and really doesn't work when someone types bogus text into the field.  I don't think that you can quite make this mirror a select situation.

So, you could use a GUID as the value and your text as the label.  It just will show the GUID in the input box.  

Does this help your situation?  I have a few more ideas for how to potentially handle it otherwise.  

Stacey Thornton

unread,
Jul 22, 2011, 11:40:56 AM7/22/11
to KnockoutJS
Solve it or not, that's VERY useful code. Hope you don't mind if I
fork that for my own storage purposes... Thank you very much! This
will do me well in studying bindings more.

It is a step closer to what I need, but not the exact solution. I
honestly wish I understood how you did this. Even reading the code, I
am confused. But in any case..

My software has a list of users. Users may have the same name, but
there are other facets that differentiate them (namely email address).
The actual goal is to populate the dropdown box with the name and the
email address in a much cleaner look (the label) and when they pick
it, to retrieve the appropriate GUID.

Ideally, I would like for the GUID to remain bound to the item
selected and keep the name. But if you're struggling with that, then
it's probably lightyears beyond my own skill level... so I may have to
rethink that entirely. I'm not 'giving up', but at this stage in my
programming career, I could not even begin to hope to compare to your
own skill in this specific area (javascript/jquery/knockout), so
something that you're telling me is difficult is probably difficult
because it isn't a good idea...

You did say you had some other ideas, I'm open to trying just about
anything.

rpn

unread,
Jul 22, 2011, 12:03:18 PM7/22/11
to knock...@googlegroups.com
Hello-
If the user picks an option from the choices, then it is pretty easy to connect it to an object.  How would you expect it to behave when the user enters bogus text or even enters some text that matches, but doesn't select it from the choices?

Stacey Thornton

unread,
Jul 22, 2011, 12:28:51 PM7/22/11
to KnockoutJS
That is a really difficult question. I think given the context of the
fact the name alone cannot be a perfect discriminator, then I would
not accept any data that was not actually clicked on to select it. In
otherwords, it would demand that they give some kind of input queue,
via a click or a tab when they were on the right choice. Otherwise, it
would discard the entered text. Then the actual label object would
have an indicator (like a text, or an icon) that kind of beckoned to
be clicked on.

rpn

unread,
Jul 22, 2011, 11:57:23 PM7/22/11
to knock...@googlegroups.com
Hi Stacey-
I was playing with this a little more and came up with this: http://jsfiddle.net/rniemeyer/YNCTY/.  Took a little more code than I wanted, but I tried to handle it as generically as possible.

You can now specify what property to use for the label, the input element value, and the value that is written to the model.  This would allow you to have a label with an email/name, show the name in the input box, and write the GUID to the model.  I made it so if someone enters invalid text and leaves the field it will clear out the model value (and input value).

I also put the update of the source items (choices) in a dependentObservable in the init function, so that I could try to keep updates to the value separate from updates to the source.  

Might do some more testing/clean up on it, as my eyes are blurry from looking at it tonight.

Stacey Thornton

unread,
Jul 23, 2011, 7:46:02 AM7/23/11
to KnockoutJS
You are utterly amazing. I don't know what to say. I just wanted to
see if someone had found a solution, I never expected you to go
through this much trouble just to help me out with it.

Stacey Thornton

unread,
Jul 23, 2011, 7:50:08 AM7/23/11
to KnockoutJS
In your fiddle, you list all of the following..

//jqAuto -- additional options to pass to autocomplete
//jqAutoSource -- the array of choices
//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

I was wondering, where are these? Are they in the core knockout
library? I did a search for them and didn't see them there, and I
didn't find them in any of the dependencies you linked to the fiddle.

On Jul 23, 6:46 am, Stacey Thornton <stacey.cielia.l...@gmail.com>
wrote:

rpn

unread,
Jul 23, 2011, 8:47:43 AM7/23/11
to knock...@googlegroups.com
Hello-
I just used a "jqAuto" binding and all of the other ones listed are referenced through additionalBindings, so they are basically options that jqAuto uses in this case (like how optionsText and optionsValue are to the options binding) .  Alternatively, I could have just passed in an object to jqAuto that contains all of these, but I was trying to emulate the options binding (and its additional bindings).  Let me know if that didn't make sense.

rpn

unread,
Jul 23, 2011, 8:54:07 AM7/23/11
to knock...@googlegroups.com
Also, in a late edit last night, I think that I broke the part where if you enter an invalid name, it will clear out the model value (and input box).  I reverted back to an earlier version.

Stacey Thornton

unread,
Jul 23, 2011, 9:10:18 AM7/23/11
to KnockoutJS
This is really incredible... I'm still completely lost, but let me see
if I grasp what is going on.

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

jqAutoSource is an arbitrarily named variable, set to the
observableArray 'myPeople'.
jqAutoValue is an arbitrary variable, set to the viewModel observable
mySelectedGuid,
jqAutoSourceLabel is another arbitrary variable, set to the
dependantObservable 'displayName' in the viewModel.
jqAutoSourceInputValue is another arbitrary variable that is set
to ... the name field of the Person object.
jqAutoSourceValue is an arbitrary variable name that is set to the
guid field of the Person object.

Are these names part of knockout, or are they literally just words you
used for the example?

Now then ...

init: function(element, valueAccessor, allBindingsAccessor,
viewModel) {
var options = valueAccessor() || {},
allBindings = allBindingsAccessor(),
unwrap = ko.utils.unwrapObservable,
modelValue = allBindings.jqAutoValue,
source = unwrap(allBindings.jqAutoSource),
valueProp = allBindings.jqAutoSourceValue,
inputValueProp = allBindings.jqAutoSourceInputValue ||
valueProp,
labelProp = allBindings.jqAutoSourceLabel || valueProp;

not sure what allBindings is doing..
unwrap is a shortcut to the ko.utils.unwrapObservable function ?
modelValue gets the VALUE, which is defined by jqAutoValue, which is
the observable in the viewModel (the guid)

source uses the shortcut to unwrapObservable to unwrap the SOURCE,
which is the entire observableArray of Person objects (??)
valueProp then tries to look at ... the variable jqAutoSourceValue ...
okay I am a bit lost at this point. Not entirely understanding what
we're examining for the last few variables..


moving down a bit more..

//handle the choices being updated in a DO, to decouple value
updates from source (options) updates
var mappedSource = ko.dependentObservable(function() {
mapped = ko.utils.arrayMap(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;
});

this defines the jQuery UI AutoComplete options as a Knockout
dependantObservable, and associates the label and value. But the
dependant observable also has an 'actualValue' field that is not part
of the jQuery UI Autocomplete framework.

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

Despite the comments, I have no idea what this is actually doing.

options.source = mappedSource();

//initialize autocomplete
$(element).autocomplete(options);

This looks a bit more familiar. Here we are actually wiring jQuery UI
Autocomplete to our element, giving it the options specified.

Okay... so some of it I'm still lost on. I'll try to interpret the
rest once I make more sense of what I have gathered so far. Man, this
is deep stuff...

rpn

unread,
Jul 23, 2011, 9:50:31 AM7/23/11
to knock...@googlegroups.com
-The names jqAutoSource, etc. are all just names that I made up trying to be descriptive about what they contain.  jqAutoSourceLabel, jqAutoSourceInputValue, and jqAutoSourceValue should all contain the name of the property that you want to use.  I wanted to make it generic, so that it was flexible and fairly robust.

-I was tired of seeing ko.utils.unwrapObservable and it was making it hard to read, so I just created a shortcut "unwrap".
-the allBindingsAccessor function that is passed into a binding gives you access to all of the bindings that were in the same data-bind.  So, I am in the "jqAuto" binding, but I am grabbing the values for all of the other bindings that were with it (jqAutoSource, jqAutoValue, etc.).

-the initial code just sets up the variables that we are need from the additional bindings and tries to enforce some defaults (if you don't say what property to use for your label, then we just default to the property that you said for the value).  By the time we get through this, we should know where to get our source items from, where to read/write the value to, and which properties to use for the label, input value, and value.

-For the mappedSource: we are passed a source and are going to build a structure from it that jQuery autocomplete likes (label and value properties).  I also added the "actualValue" property (arbitrary name), so that we have access to the real thing that we want to store on our model (the GUID for you).  Then, when someone makes a selection, you have access to that object (with label, value, and actualValue).  The reason that I played with putting this in a dependentObservable instead of just doing it in the update method was to try to separate the updates that happen when the value is updated vs. when the options are updated.  For example, when using the options binding with the value binding on a select element, every time that the value is updated, the options are re-evaluated as well.  This can cause performance problems.  So, I put this in a dependentObservable, so that if the items that makes up the choices change, jQuery autocomplete will have an accurate list of options.  The update method has the logic for handling the case that the model value was updated (either set initially or programmatically changed, so in your case the GUID is changed by something other than this box).

So, here are the tasks that I was trying to accomplish:
1- initialize the autocomplete with appropriate options (including a proper source array with label & value)
2- handle what happens when someone makes a selection (update the model value with the appropriate value)
3- handle someone entering bogus text into the box (when change event fires, try to see if the input value seems valid)
4- handle when the underlying options are updated (need to rebuild the source array and give the new structure to autocomplete)
5- handle when the value is updated elsewhere (initially set or programmatically changed).  For example, you set the GUID to something else programmatically, we need to find the appropriate item and set the input value to the name.







Stacey Thornton

unread,
Jul 23, 2011, 10:35:00 AM7/23/11
to KnockoutJS
Okay, I think I get it now! This is so helpful! I don't know how to
thank you enough for what you've done.

studgeek (David Rees)

unread,
Sep 12, 2011, 7:24:22 PM9/12/11
to knock...@googlegroups.com
I think I found an issue in your excellent autocomplete example. It doesn't detect when the source observable array changes. Here are the changes I made at http://jsfiddle.net/studgeek/rwvdd/.

I added an add button to the example so we can see it work :).

The mappedSource dependencyObservable wasn't accessing the source array observer internally, so therefore it wasn't creating a subscription on it. So any array-level changes (push/delete) were not detected. So I simply moved the source unwrap to inside the mappedSource dependencyObservable.

The options.change callback was keeping a local (closure) copy of the source array, so it also was missing out on any source array changes. I modified it to use the mappedSource's value instead and updated its comparison function to use the mapped item's value.

I'm still new with KO, so rpn please correct me where I am wrong :).

d

rpn

unread,
Sep 12, 2011, 7:39:56 PM9/12/11
to knock...@googlegroups.com
Hi David-
You are exactly right about the dependentObservable issue.  It needs to access the observableArray's value from inside the DO or it would never get re-evaluated.  I think that when I saw that I needed to use "source" in two spots, I decided to unwrap it at the top without seeing the consequences.    

For the other issue, it does not hurt to change it to mappedSource, but when you are dealing with non-primitives you would always get the current object (array for us) and not a copy of it even in the case of a closure.  

I updated my fiddle.  Thanks for pointing this out.

studgeek (David Rees)

unread,
Sep 12, 2011, 7:53:50 PM9/12/11
to knock...@googlegroups.com
On Monday, September 12, 2011 7:39:56 PM UTC-4, rpn wrote:
For the other issue, it does not hurt to change it to mappedSource, but when you are dealing with non-primitives you would always get the current object (array for us) and not a copy of it even in the case of a closure.  


Just to make sure I am not missing anything, you only get the current object if you call/unwrap the observer right? The original code had source as the unwrapped object as follows:
 source = unwrap(allBindings.jqAutoSource),

In that case it would not have been updated, right? I'm just asking to verify that init() is only called once - unlike update(). Heck, I just found out about update being essentially a giant DO a couple days ago (and just started with KO last week :).

d

rpn

unread,
Sep 12, 2011, 8:09:06 PM9/12/11
to knock...@googlegroups.com
The one thing to understand is that unwrapping the observableArray is going to give you a reference to the underlying array.  That underlying array is going to be the same wherever you access it (in "init", in "update", or anywhere else).  Another way to say it is that source in this case is not a copy of the array, it is a reference to the underlying array.  Hope this helps.  Let me know this still seems unclear. 

init() is only called once.

You seem to be getting how KO works quite well so far.  

studgeek (David Rees)

unread,
Sep 13, 2011, 3:32:25 PM9/13/11
to knock...@googlegroups.com

On Monday, September 12, 2011 8:09:06 PM UTC-4, rpn wrote:
The one thing to understand is that unwrapping the observableArray is going to give you a reference to the underlying array.  That underlying array is going to be the same wherever you access it (in "init", in "update", or anywhere else).  Another way to say it is that source in this case is not a copy of the array, it is a reference to the underlying array.  Hope this helps.  Let me know this still seems unclear. 



I see what you are saying about them all pointing at the same array. I think I didn't describe the 2nd issue I was trying to fix clearly enough in my previous email. The problem isn't catching changes to the array its catching when you change which actual array instance you are using (e.g. array's parent object changes). In any case, your updated code catches it.

 
init() is only called once.


Cool, that is what I wanted to double check. Makes sense given update() is called the first as well.

 
You seem to be getting how KO works quite well so far.  


Thanks :). I have worked with the paradigm several times before, never in JS though. Your implementation is very slick. It's interesting that data-bind strings are really just literals. I tweaked my IDE (WebStorm) to recognize that and now it gives me autocompletion and refactoring right inside them :).

d

studgeek (David Rees)

unread,
Sep 13, 2011, 3:40:32 PM9/13/11
to knock...@googlegroups.com
Ok, another tweak. I wanted use autocomplete with object values like you can the options binding. It just needed a tweak in the value set at the end of update, which also allowed me to not need an empty object when no matching value is found in the options.

The updated jsfiddle with example code showing add and delete is here http://jsfiddle.net/studgeek/rwvdd/14/. Changes to the binding are in the last 9 lines of update().

Thanks again for the autocomplete example, its been a great learning tool.

d

rpn

unread,
Sep 13, 2011, 4:35:30 PM9/13/11
to knock...@googlegroups.com
Thanks David.  I had intended it to work properly with objects like the options binding.

I updated the original: http://jsfiddle.net/rniemeyer/YNCTY/  and the AJAX one: http://jsfiddle.net/rniemeyer/MJQ6g/ with a similar tweak.

I did catch what you meant earlier about how replacing the entire array would cause a failure and had factored that into the earlier fix.  Thanks for digging into the code! 


studgeek (David Rees)

unread,
Oct 4, 2011, 3:37:37 PM10/4/11
to knock...@googlegroups.com
One more tweak, I added the following line to the mappedSource DO. You can see my updated jsfiddle at http://jsfiddle.net/studgeek/rwvdd/18/.
        }, null, { 'disposeWhenNodeIsRemoved' : element });

The line is needed to ensure the DO is disposed when the autocomplete is removed (template or window close). Unfortunately KO only does this automatically for the DOs it creates (e.g. binding DOs).

d

nevf

unread,
Jan 13, 2012, 2:00:46 AM1/13/12
to knock...@googlegroups.com
Hi Ryan,
I've found a problem with ko.bindingHandlers.jqAuto which shows up when jqAutoSource is a simple array [ 'item1', 'item2', .. ] vs. an array of objects.

The line in options.change()
 return unwrap(item[inputValueProp]=== currentValue;   
needs to be:
 return unwrap(inputValueProp ? item[inputValueProp] : item=== currentValue;   

Without this when the <input> item looses focus it's value is cleared.

rpn

unread,
Jan 13, 2012, 9:53:23 AM1/13/12
to knock...@googlegroups.com
Thanks for pointing that out!

nevf

unread,
Jan 13, 2012, 10:20:41 AM1/13/12
to knock...@googlegroups.com
Hi Ryan,
I have a new issue in that I want to use a function for jqAutoSource instead of an array. See here. This is so that I can do my own custom result matching. This breaks in the mappedSource DO and also probably in the mappedSource.subscribe. Would you be able to update ko.bindingHandlers.jqAuto() to allow jqAutoSource  to be a function? Maybe mappedSource isn't required at all in this use case.

Neville

rpn

unread,
Jan 13, 2012, 10:29:40 AM1/13/12
to knock...@googlegroups.com
Hi Neville-
Does the "jqAutoQuery" option in this sample meet your needs?  It still uses an observableArray as the source, but let's you pass a function that you can use to populate the array.

Neville Franks

unread,
Jan 13, 2012, 3:31:56 PM1/13/12
to knock...@googlegroups.com
Hi Ryan,
Many thanks for the quick response. It looks like this may do the trick. I'll let you know once I've tried it in my code.


On Sat, Jan 14, 2012 at 2:29 AM, rpn <rnie...@gmail.com> wrote:
Hi Neville-
Does the "jqAutoQuery" option in this sample meet your needs?  It still uses an observableArray as the source, but let's you pass a function that you can use to populate the array.



--
 Neville Franks, Author of Surfulater - Your off-line Digital Reference Library
 Soft As It Gets Pty Ltd,  http://www.surfulater.com - Download your copy now.
 Victoria, Australia    Blog: http://blog.surfulater.com 


evanlarsen

unread,
Mar 12, 2012, 8:30:32 PM3/12/12
to KnockoutJS
rpn,

I'm having trouble implementing your ajax auto complete example. I'm
pulling down from the AJAX call an array of objects when the user
types in the input field but immediately after the text they type in
disappears.

I traced it down to this line:
$(element).val(modelValue && inputValueProp !== valueProp ?
unwrap(modelValue[inputValueProp]) : modelValue.toString());

modelValue && inputValueProp !== valueProp equals true and then my
unwrap(modelValue[inputValueProp]) is undefined. So, it keeps erasing
what I type in immediately after I type it. The drop down works and
shows a filter of the elements brought down from the ajax request but
it doesnt give me the chance to keep typing farther. If I type after
it erases.. then it will start a new search.

Do you have any ideas as to why this might be happening. I've been
staring and stepping through the code for hours and i cant figure it
out. sadly

rpn

unread,
Mar 12, 2012, 9:33:20 PM3/12/12
to knock...@googlegroups.com
Maybe you can share or send me a sample of the code that you are using or try to demonstrate in a fiddle.  You should be specifying an observable in "jqAutoValue" to be populated by the choice that a user makes.  

evanlarsen

unread,
Mar 13, 2012, 9:08:28 AM3/13/12
to KnockoutJS
Well, I put it in jsFiddle but.. its not working in there. I've looked
at your example which works and compared it to mine and the only
difference I can see is that your populating a custom object in the
ajax response.

http://jsfiddle.net/evanlarsen/7skjQ/

Is it necessary to pass an array of observables to the 'sourceArray' ?

rpn

unread,
Mar 13, 2012, 10:16:05 AM3/13/12
to knock...@googlegroups.com
The problem in your fiddle was finding the "getCities" function as it is not on your view model.  Under "Choose Framework" if you pick "no wrap (body)" instead of "onLoad", then getCities will be global.  Otherwise, you would want to attach it to your view model.  

Here it is working:  http://jsfiddle.net/rniemeyer/7skjQ/5/.  Hopefully that can help narrow down where you are seeing issues.

evanlarsen

unread,
Mar 13, 2012, 10:38:02 AM3/13/12
to KnockoutJS
rpn your a genius.

Sorry about my jsfiddle noobness. That was my first time using it.

The example you just provided does exactly what mine does on my local
machine, which I'm trying to fix. For some reason, immediately after
you type and it gets results from the ajax request the text in the
input box disappears temporarily. Sometimes it reappears. So, if
your typing kinda slow.. like 1 letter every second then you will
start new search terms in the middle of what your typing.

Is there a way to get it to work, where it will retain what your
typing and not clear the input field upon every ajax request?

rpn

unread,
Mar 13, 2012, 9:55:42 PM3/13/12
to knock...@googlegroups.com
Give this one a shot: http://jsfiddle.net/rniemeyer/7skjQ/6/

evanlarsen

unread,
Mar 13, 2012, 10:31:35 PM3/13/12
to KnockoutJS
rpn,

thank you again!, That works perfect. I just came up with a solution
just a min ago but it wasnt as good as yours. Thanks again.
Message has been deleted

ricardas....@gmail.com

unread,
Jul 24, 2012, 11:48:28 AM7/24/12
to knock...@googlegroups.com
Hello, I am using almost the same autocomplete sollution, except that I have jqAutoQuery property in binding handler (I found such binding handler somewhere in the Internet, but I cannot remember where for now). Anyways my handler has same code comments, same variables inside the handler. I have one problem when user types wrong text (I expect to clear the text as code is supposed to work).

So first time user enters wrong text (query returns 0 records), this works fine:
            if (!matchingItem) {
                writeValueToModel(null);
            }

However when user types again incorrect text, input field is not cleared out. And I think this is because  
writeValueToModel(null) does not change anything - the value already is null. So observable variable does not have to notify UI input field. And this input field is left with wrong value..

my current workaround is
             if (!matchingItem) {
                writeValueToModel("-");
                writeValueToModel(null);
            }

Do you have any ideas what I am doing wrong? Or any better solutions, to force notification work properly.

vald...@gmail.com

unread,
Jun 19, 2013, 2:13:21 PM6/19/13
to knock...@googlegroups.com, evan....@gmail.com
I would like to say that I have been hitting my head against the wall for literally a day and a half trying to figure this one out. This solution fixed it. THANK YOU!
Reply all
Reply to author
Forward
0 new messages