Add striped styling to a list of items
Asked Answered
S

6

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>
Slit answered 22/6, 2011 at 6:16 Comment(0)
I
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/

Illtimed answered 22/6, 2011 at 12:51 Comment(4)
how do we ensure that the template rendering is done before the stripes are applied? or is that even an issue?Thales
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.Illtimed
I saw this, but I was hoping for a simpler approach. I guess I will go with this.Slit
This solution does work but I found ZiglioNZ's answer much simpler and it works the same. https://mcmap.net/q/523589/-add-striped-styling-to-a-list-of-itemsKed
P
59

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) }">
Propeller answered 12/6, 2012 at 21:29 Comment(3)
$index is documented at Binding context: link - Thanks @wescPropeller
This has always been my preferred method, and I don't see what it doesn't offer as compared to the accepted answer.Approachable
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 thatPropeller
I
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/

Illtimed answered 22/6, 2011 at 12:51 Comment(4)
how do we ensure that the template rendering is done before the stripes are applied? or is that even an issue?Thales
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.Illtimed
I saw this, but I was hoping for a simpler approach. I guess I will go with this.Slit
This solution does work but I found ZiglioNZ's answer much simpler and it works the same. https://mcmap.net/q/523589/-add-striped-styling-to-a-list-of-itemsKed
P
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) }">
Pentatomic answered 7/4, 2012 at 7:1 Comment(0)
W
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>
Wartburg answered 27/10, 2012 at 13:29 Comment(0)
T
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)

Thales answered 22/6, 2011 at 12:46 Comment(2)
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.Slit
I think knockoutjs re-renders the whole template when you add/delete something from the arrayThales
C
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>
Crossopterygian answered 24/8, 2012 at 14:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.