25

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>
Jeroen
  • 60,696
  • 40
  • 206
  • 339
Mike Flynn
  • 22,342
  • 54
  • 182
  • 341

6 Answers6

58

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) }">
ZiglioUK
  • 2,573
  • 4
  • 27
  • 32
  • 4
    $index is documented at Binding context: [link](http://knockoutjs.com/documentation/binding-context.html) - Thanks @wesc – ZiglioUK Jun 22 '12 at 03:59
  • 3
    This has always been my preferred method, and I don't see what it doesn't offer as compared to the accepted answer. – T.W.R. Cole Sep 04 '13 at 19:41
  • 1
    Thank you, I think the accepted answer pre-dates my answer, so unless the original poster reviews it, it'll stay as it is. Not sure if anybody else can change that – ZiglioUK Sep 05 '13 at 03:51
21

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/

RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • how do we ensure that the template rendering is done before the stripes are applied? or is that even an issue? – neebz Jun 22 '11 at 14:43
  • 1
    Just need to put the `stripe` binding after the template binding and it will be fine. The bindings do run left to right, but Steve has said that that is not something that he is trying to guarantee necessarily. Otherwise, there is a version in the fiddle called `templateWithStripe` that wraps the template binding and absolutely guarantees the order. It probably provides the least verbose syntax. – RP Niemeyer Jun 22 '11 at 15:17
  • I saw this, but I was hoping for a simpler approach. I guess I will go with this. – Mike Flynn Jun 22 '11 at 16:26
  • This solution does work but I found ZiglioNZ's answer much simpler and it works the same. http://stackoverflow.com/a/11005167/346561 – Jesse Webb Nov 17 '15 at 18:05
3

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) }">
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
2

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>
Mark SRQ
  • 21
  • 1
0

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>
andrewsi
  • 10,807
  • 132
  • 35
  • 51
netlyonel
  • 155
  • 1
  • 2
  • 8
0

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)

Community
  • 1
  • 1
neebz
  • 11,465
  • 7
  • 47
  • 64
  • This may work for a static list, but if I am removing and adding items then I could have two even or odd rows in a row. – Mike Flynn Jun 22 '11 at 16:26
  • I think knockoutjs re-renders the whole template when you add/delete something from the array – neebz Jun 23 '11 at 07:58