Saturday, January 23, 2016

Checkbox List in Foreach Knockout


Displaying Selected Items

So we have our list of selected items and we can retrieve the list from our selectedItemsfunction.  This is all we need if we’re doing most of our data work behind the scenes using plain JavaScript.  We can easily grab all the selected items and send a data package to the server using AJAX.  If we want to display our list of selected items on the page separately from the list of checkboxes, then a little bit of refactoring is in order.
Any time we’re displaying changes in the state or data of our view models, we should be working through Knockout.  Right now our view model is mostly plain JavaScript.  We want to display a non-editable listing of all the selected items, which updates automatically when the selection changes; we want our selected list to be observable.  Since our UI and model decisions have us working both with checkboxes and returning secondary array of selected items, we can do this in a bit less code.
The first thing we want to do in our refactor is create an observableArray property on our view model which will hold the selected items.  We’ll just comment out our selectedItemsfunction which we’re replacing to show the difference in code. Since we wont be using theselected property on the view models in our array anymore, we can get rid of it. We’ll also get rid of the name property too and just leave an array of strings. I’ll take a look at what happens if we don’t do that in a moment.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var viewModel = function(){
    var self = this;
    self.apps = [
        'Freckle',
        'Beanstalk',
        'DropBox',
        'Postmark'
    ];

    self.selectedItems = ko.observableArray([]);

    //using javascript
    /*self.selectedItems = function(){
        var selectedItems = [];
        for(var i = 0, len = self.apps.length; i < len; i++) {
            if(self.apps[i].selected){
                selectedItems.push(self.apps[i]);
            }
        }
        return selectedItems;
    }*/
}
Much simpler.
Now we can use a great feature of the checked binding to populate our new selectedItemsobservable array.  The checked binding can bind one checkbox to one boolean property, but it can also bind a checkbox to an array.  By switching the target of our checked binding to selectedItems, we now have Knockout automatically adding and removing the bound items or view models to our selectedItems array for us. It’s important to notice that the selectedItems array is in the parent context when we’re inside our foreach binding for the apps array. We’ll need to use the $parent variable to reach it.
There’s one more catch with this approach; the checkboxes’ html value attribute needs to be unique in order for this to work the way we want. We can address this by adding a valuebinding to our checkboxes and set it to the string we’re currently binding by using the Knockout$data variable.
1
2
3
4
5
6
7
<ul class="unstyled" data-bind="foreach: apps">
    <li>
        <label>
            <input type="checkbox" data-bind="value: $data, checked: $parent.selectedItems" />
        </label>
    </li>
</ul>
We can then bind our selectedItems array to our secondary view of the data, and we’re done!
1
2
3
4
<h3>The Results</h3>
<ul class="unstyled" data-bind="foreach: selectedItems">
    <li data-bind="text:$data"></li>
</ul>
Nice.

How to Screw This Up

As you saw in the last section, everything needs to be just right for this “low code” approach to work. We may need to use $parent and $data in our bindings. We have only strings in our source data array, and we get strings in our selectedItems array. What if we hadn’t simplified our data down to just strings? What if we were still working with objects like this?
1
2
3
4
5
6
7
8
9
10
var viewModel = function(){
    var self = this;
    self.apps = [
        {name:'Freckle'},
        {name:'Beanstalk'},
        {name:'DropBox'},
        {name:'Postmark'}
    ];
...
}
The first place we’d see the impact here is in the bindings. Instead of using $data, we’re back to binding to the name property. If you’re familiar with binding html select tags, you might expect that we can bind our entire object as the value of the checkbox and see that object show up in our selectedItems array. Unfortunately, as of Knockout 2.0.2 this isn’t supported. The value binding when used on a checkbox will only take a string, and so our selectedItemsarray will only contain strings even if our source data is an object.
1
2
3
4
5
6
7
8
9
10
11
12
13
<ul class="unstyled" data-bind="foreach: apps">
    <li>
        <label>
            <!-- bind the name property of our object as the checkbox value -->
            <input type="checkbox" data-bind="value: name, checked: $parent.selectedItems" />
        </label>
    </li>
</ul>

<ul class="unstyled" data-bind="foreach: selectedItems">
    <!-- our selectedItems array contains only strings, not full objects -->
    <li data-bind="text:$data"></li>
</ul>
If you wanted your selectedItems array to contain the object, you’re back to using a function to find the items from the original data array as before. You can match the name or an Id if that’s what your checkbox value was bound to.

Nice Trip. Where Are We?

So we went from a custom filter function, to quick binding using checked against an array, and then back to using functions when our source data became complex. Every change in our code was predicated by a different need in the UI, or a different structure in the view models. Ultimately, I’d love to see the checked binding in the Knockout library support full objects the way that the options binding does. We’ve got two more blog posts to go in this series. Maybe we’ll get there yet.



No comments:

Post a Comment