What would be the best way to stripe a list with KnockoutJS? The class on the div below should be even or odd depending where it is in the list, and update when adding or removing items.
<div class="Headlines loader"
data-bind="css: { loader: headlines().length == 0 },
template: { name: 'recentHeadlinesTemplate',
foreach: beforeHeadlineAddition,
beforeRemove: function(elem) { $(elem).slideUp() },
afterAdd: slideDown }">
</div>
<script type="text/html" id="recentHeadlinesTemplate">
<div class="even">
${Title}
</div>
</script>
There was a topic for this on the KO forums a while back here: https://groups.google.com/d/topic/knockoutjs/cJ2_2QaIJdA/discussion
The solution that I had was a custom binding. There were a couple variations on it, but it basically would look like:
ko.bindingHandlers.stripe = {
update: function(element, valueAccessor, allBindingsAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()); //creates the dependency
var allBindings = allBindingsAccessor();
var even = allBindings.evenClass;
var odd = allBindings.oddClass;
//update odd rows
$(element).children(":nth-child(odd)").addClass(odd).removeClass(even);
//update even rows
$(element).children(":nth-child(even)").addClass(even).removeClass(odd);;
}
}
and be used like:
<ul data-bind="template: { name: 'itemsTmpl', foreach: items }, stripe: items, evenClass: 'light', oddClass: 'dark'"></ul>
Sample here with 3 variations of this binding:
http://jsfiddle.net/rniemeyer/HJ8zJ/
I've found a function that returns an index when iterating with foreach, so you can apply even and odd classes in a reasonably compact way, for example:
<tr data-bind="css: { 'even': ($index() % 2 == 0) }">
One easy way to do this is to add a computed observable that adds an index to each element, e.g.
self.logLines = ko.observable(logLinesInput);
self.enhancedLogLines = ko.computed(function() {
var res = [];
$.each(self.logLines(), function(index, ll) {
res.push(new LogLine(index, ll));
});
return res;
}, self);
In my case LogLine()
creates an object with an index field and the other fields that were in the original object.
Now you can easily add zebra stripes to your output:
<tr data-bind="css: { odd: (index % 2 == 1), even: (index % 2 == 0) }">
Thanks for the helpful posts. I wanted to mention that css can do a good job of striping but the embedded 'if' only seems to function after the row has been rendered. Therefore, using $index or the css odd/even capabilities don't yield the desired results. Without using a template I found that you can wrap the KO logic around the row so that the logic occurs before the row is counted.
<tbody data-bind="foreach: viewModel.configuration().items()"">
<!-- ko if: $data.part() != '' -->
<tr>
<td data-bind="text: $index"></td><td data-bind="text: $data.part()"></td>
</tr>
<!-- /ko -->
</tbody>
You could use the {{if}} and {{else}} conditional statements inside the template to set the class of the div.
Also you will need to extend your View Model to include a function which returns the index of your current item, which would tell you whether it's odd or even. (Something like this)
here is a full example :
<ul class="pickupPointHours" data-bind="foreach: Items">
<li data-bind="css: { lineEven: ($index()%2 === 0), lineOdd: ($index()%2 === 1)}">
<span class="pickupPointDay" data-bind="text: TextProperty"></span>
</li>
</ul>