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