The Lanham Factor
The (ir)rational thoughts of a (not-so)mad man

Binding Checkboxes with KnockoutJS

Thursday, August 8, 2013 12:47 PM

Recently I had occasion to bind a list of checkbox input elements to a KnockoutJS observableArray.  It actually took me a great deal of time to get the implementation working.  In this post I am going to cover the issues I experienced.  Then I will explain how implement a surefire checkbox-KnockoutJS binding solution.  This post assumes you have a working knowledge of KnockoutJS.  If you are new to KnockoutJS, please acquaint yourself at http://www.knockoutjs.comNOTE: Try is yourself on JSFiddle http://jsfiddle.net/codesailor/H7wLT/37/.

The Solution

I need to display a list of available associated items to associate with a base or “root” item.  For example, think of a “teacher” who is associated with multiple “classes” for providing instruction.  So the plan is to show the basic teacher information on the left side of the screen with the list of items on the right side of the screen.  What is needed, then, is a list of available classes as well as a list of classes already associated with the teacher.

image

Problem A – Visual Checkbox State Never Changes

My first issue revolved around trying to use the KnockoutJS checkbox binding inappropriately.  I decided to use a single observableArray wherein each array element has a “Selected” observable.  Then I “merge” the list of available classes with the list of associated classes by setting the Selected property to “true”.  This method actually works for displaying the initial state

image

You can see that the “click” event is wired to the “toggleClassassociation” method in my viewModel. That method makes a Web API call to toggle the teacher-class association.  However, no matter what I did, the visual state of the checkbox never changed.  When I refresh the page the initial state displays correctly.  The back-end code is working to make/break the associations.  So what is going on?

I found on a forum that I need to return “true” from the “click” event handler.  That did cause the visual state to change but it wasn’t enough.

Problem B – Misunderstanding the Binding

It turns out that I truly did not understand the binding for checkboxes.  There are a few key points in checkbox (and radio button) binding when using KnockoutJS.

  • If binding the checkbox list to an observableArray, the “checked” attribute needs to be the observableArray, not a property name.  KnockoutJS will automatically update the observableArray based on the state of the checkbox.
  • KnockoutJS automatic updating of the bound array is dependent on the “value” attribute of the element.  Notice the code screenshot above that only the “checked” attribute is set.  In order to bind to an array you need to use the “value” attribute as well.

Let’s look at the code snippet for the working solution.

image

There are a few key items to point out in this implementation.

  1. The parent <div> uses an observableArray (“ClassesPicklist”) that is different from the binding observableArray (“SelectedTeacher().ClassIds”).  This isn’t a problem because KnockoutJS only looks at the array elements
  2. The “value” attribute is used but the actual value is cast to a string.  That took me a while to figure out.  My initial implementation used the numeric ID values.  I started with using actual JavaScript objects and when that didn’t work I switched to numeric values only.  That didn’t work either and I decided to use strings as in the example on the KnockoutJS Web site.
  3. How does “checked” get set?  This is the magic of KnockoutJS.  As stated above, when the “checked” attribute is bound to an observableArray, KnockoutJS will search the array for any element that matches the “value” attribute.  Again, it only seems to work if they are both of type string.

Let’s look at the method called on “click”.

image

Notice that by default, KnockoutJS still passes the current data item (“cls”) to the function when it is called.  But remember that the items are bound to a different array than the checkbox binding.  The “ClassesPicklist” observableArray contains the values that are displayed in the browser.  So I use the “Selected” property to determine which action to take.  Notice, also, that I swap the value of “Selected” just before returning from the function.

This operation is not required.  I could scan both arrays and toggle all items not in the checkbox-bound array (“SelectedTeacher().ClassIds”).  But that would cause a lot of unnecessary traffic and overhead and it’s just as easy to track the items manually as shown above.

And speaking of tracking the items, when does “SelectedTeacher().ClassIds” get updated?  KnockoutJS does that for us.  Note that the “checked” attribute is bound to the array so KnockoutJS performs some wonderful magic and takes care of it for you.  If you check the checkbox KnockoutJS adds the value in the “value” attribute to the observableArray associated with the “checked” attribute.  So if the class ID is, for example, 13 then the value of that item will be “13” (remember, strings) and the “SelectedTeacher().ClassIds” array will contain “13”.  If it is already checked and you click it then the value “13” is removed from the array.

Summary

So to wrap it up, here are the key points to remember when binding a list of checkboxes using KnockoutJS where each checkbox represents a child element of some parent object:

  1. Use a separate observableArray for the binding and associated with array with the “checked” attribute.
  2. Use a value that can be represented as a string in both the bound array and the “value” attribute of the checkbox element.
  3. If you need to perform some server-side operation, return true from the “click” event handler.
  4. Separately track those items which are and aren’t associated with the root object.

So give it a try yourself using JSFiddle! http://jsfiddle.net/codesailor/H7wLT/37/

Obviously you should feel free to respond and or contact me if you have any trouble running the sample or any issues or questions.




Feedback

No comments posted yet.


Post a comment