Zebra Table Showdown

Posted on by

As a follow-up to the previous event selector showdown comes a new Zebra Table Showdown.

The Challenge:

Making all tables on a page have striped (“Zebra”) backgrounds (different coloring on every odd row).

The Rules:

  • The odd/even styling should be done by adding a class for the ‘odd’ numbered table rows.
  • This must be able to work on multiple tables. (It’s not as simple as finding all rows then going through every odd one, otherwise an even row could become highlighted in the next table).
  • The code should be done as elegantly as possible, using as much of a libraries’ functionality as possible. Speed is not an issue.

Intended Result:
zebra-fig1.gif

Plain DOM Scripting: (demo)

var tables = document.getElementsByTagName("table");
for ( var t = 0; t < tables.length; t++ ) {
  var rows = tables[t].getElementsByTagName("tr");
  for ( var i = 1; i < rows.length; i += 2 )
    if ( !/(^|\s)odd(\s|$)/.test( rows[i].className ) )
      rows[i].className += " odd";
}

Note: This includes a check to make sure that the 'odd' class doesn't already exist on the table row. This is taken care of by all modern libraries.

Yahoo UI: (demo)

var tables = document.getElementsByTagName("table");
for ( var t = 0; t < tables.length; t++ ) {
  var rows = tables[t].getElementsByTagName("tr");
  for ( var i = 1; i < rows.length; i += 2 )
    YAHOO.util.Dom.addClass( rows[i], "odd" );
}

Note: Although, their own developer site disagrees.

Dojo: (demo)

var each = dojo.lang.forEach;

each(document.getElementsByTagName("table"), function(table){
  each(table.getElementsByTagName("tr"), function(row,i){
    if ( i % 2 == 1 )
      dojo.html.addClass( row, "odd" );
  });
});

Note: A common function was assigned to a variable in order to make the code shorter.

Prototype (1.4.0): (demo)

$A(document.getElementsByTagName("table")).each(function(table){
  $A(table.getElementsByTagName("tr")).each(function(row,i){
    if ( i % 2 == 1 )
      Element.addClassName( row, "odd" );
  });
});

Mochikit: (demo)

var byTag = getElementsByTagAndClassName;

forEach( byTag("table"), function(table) {
  var rows = byTag( "tr", null, table );
  for ( var i = 1; i < rows.length; i += 2 )
    addElementClass( rows[i], "odd" );
});

Note: A common function was assigned to a variable in order to make the code shorter.

Prototype (1.5.0): (demo)

$$("table").each(function(table){
  Selector.findChildElements(table, ["tr"])
    .findAll(function(row,i){ return i % 2 == 1; })
    .invoke("addClassName", "odd");
});

mootools: (demo)

$$("table").each(function(table){
  $ES("tr", table).each(function(row,i){
    if ( i % 2 == 1 )
      row.addClass( "odd" );
  });
});

jQuery: (demo)

$("tr:nth-child(odd)").addClass("odd");

If you feel as if you have a more elegant solution than the ones posted here, please post them below so that the listing can be updated.


New Submissions:

These are new entries that have been submitted by other readers.

AJS: (demo)

AJS.map(AJS.$bytc("table"), function(table) {
 AJS.map(AJS.$bytc("tr", null, table), function(row, i) {
   if ( i % 2 == 1 && !/(^|\s)odd(\s|$)/.test( row.className ) )
     AJS.setClass( row, "odd" );
 });

Prototype 1.5.1: (demo)

$$('tr:nth-child(odd)').invoke('addClassName', 'odd');

46 thoughts on “Zebra Table Showdown

  1. Bradley Sepos on said:

    This reminds me of Steve Jobs’ Apple keynote last year when he demoed the new Apple remote. He compared it’s small form factor and only six buttons to other remotes in the industry that had 40+ buttons, and said, “This may be the best visual of what Apple is all about, ever.”

    This is jQuery. This is what we’re about. :)

  2. John Resig on said:

    @Sam: It’s not quite that simple – doing just :odd will count over all rows and ignore the even/odd count of any particular table. For example, if table 1 has 8 rows in it, then the first row of table two would be marked as ‘odd’ – which is incorrect. The :nth-child(odd) CSS selector allows you keep the count relative to the size of each table (without having to do any nasty looping business on your part).

  3. Noah Lazar on said:

    The nth-child approach is probably the most elegant one possible. I’ve been using this code for months on my sites:
    $(“table.data”).each(function(){
    $(“tr:odd”, this).not(“.even”).addClass(“odd”);
    $(“tr:even”, this).not(“.odd”).addClass(“even”);
    });

    The class name on the table is not needed if you want to style every table — including layout ones that your clients may accidently create. I also typically have the “even” class for an additional styling options. The not function allows for manual overwriting in the HTML if needed.

    However, I assume using the “each” function loop is probably slower — I can’t think of anything simpler than nth-child. Thanks for the tip!

  4. By the way John, you forgot to include the .className attribute after rows[i] in your plain DOM example regex check. Otherwise it won’t be looking for the “odd” class.

    Also something else for everyone to think about… ever consider a check for nested data tables? With plain DOM scripting, I used this condition to make sure the row’s parent (or tbody’s parent) is the same table you are dealing with:
    if (rows[i].parentNode.parentNode == tables[t] || rows[i].parentNode == tables[t]) …

    In jQuery, you could probably use XPath or the parent() and is() functions to make sure you only hit the direct descendant rows of a table or tbody tag.

  5. John Resig on said:

    @Noah: Good catch with the .className – I seriously make that mistake all the time.

    I’m kind of confused by your nested table question – are you wondering about only styling rows that are the direct child of a particular table (should the tables be nested)? I would probably use something like this, to style one table:
    $(“table > tr:odd”).addClass(“odd”);
    Let me know if that’s what you mean.

  6. Plain DOM Scripting Alternative.

    Not tested, but this should work? I know it’s not exactly ‘elegant’, but sometimes I prefer shorter code for simple tasks, as opposed to elegant code. Which is why I use jQuery :P

    var x = 0, y = 0;
    
    while((t = document.getElementsByTagName("table")[x++]) && !(y = 0))
      while((r = t.getElementsByTagName("tr")[y += 2]) && !r.className.match(/odd/))
        r.className += " odd";

    Note: I’m not calling jQuery ’short but not elegant’, just that it appeals to me because of how succinct it is at many many tasks.

  7. James Asher on said:

    It seems to me that, from a users perspective, they wouldn’t care how it was coded as long as it 1) worked and 2) was fast. It seems odd to me that you state that speed is not an issue. If one line is all it takes, that’s great from a developer perspective, but the user doesn’t care.

    I’m curious to know how fast each one of these libraries do this work.

  8. John Resig on said:

    @James: I stated that just to make the ‘showdown’ simpler. For example, Prototype 1.5’s solution would be faster if it didn’t use:
    .findAll(function(row,i){ return i % 2 == 1; })
    and instead just looped through every odd row. However, I find that doing it this way best embodies the principles behind the Prototype library – in the end, creating some interesting code that you can really sink your teeth into.

    In the end, nothing is going to be faster than the DOM-only method. The problem with analyzing things like speed, filesize, line count is that they are highly debatable. Simply because I used the variable ‘table’ in some of the above examples makes them longer – is that really fair? Or is it fair that some more elegant (but slower) code is picked for one library, but not another? That is not a flamewar that I want to get into.

    Plus, it’s absolutely safe to say that any example presented here is more than fast enough to get the job done, and provide a good experience for the user. In reality, you’ll just be splitting hairs over milliseconds of difference.

  9. Noah Lazar on said:

    @John: your example is right. I’ve had clients in the past place miniature data tables within other data table cells. It’s not common, but I’ve seen it. With regular zebra striping code, the “inner” table rows will be counted with its parent and then screw up the even/odd count.

    $(“table > tr:odd”).addClass(“odd”); is more of what I’m looking for, but it would need a little work when you account for tbody tags that are manually coded or dynamically inserted into the browser’s DOM.

    @avidal: Does your simpler approach read the document-level DOM on every loop? I mean, does calling document.getElementsByTagName(“table”) cause a browser to have to parse the entire document again before finding the [x+1] table. By manually setting variables for the smaller objects, I ‘believe’ that you save time from having to keep traversing down the entire DOM. But as John said, it’s probably just milliseconds that won’t make a difference on most pages. I like the simplicity of it though!

  10. John Resig on said:

    @Noah: The nice thing about using :nth-child(odd) is that it completely ignores any embedded-table problems, since all even/odd calculations are done relative to the current table anyway. This way, even if the table rows are grabbed all out of order, the results will still be the same.

  11. Giel Berkers on said:

    And yet another example why jQuery is so great.

    All shall bow and tremble for jQuery!

  12. Bill I on said:

    Not super-crazy news, but on a small page with little data, the difference in load time was what stuck out most clearly to me.

    Even on a fast connection, there’s a _noticeable_ lag when loading the Prototype, Dojo, Mochikit, and Yahoo demos. mootools and jQuery are clearly faster-loading, but not quite as quick as pure-DOM (for obvious reasons).

    I haven’t really seen a lot of “side-by-side” demos, so this was noteworthy IMHO.

  13. John Resig on said:

    @Bill: Just as a note, all pages use the same window.onload = function(){}; technique, just to give them a level playing ground. So the lag that you noticed may be in relation to that, rather than the actual libraries themselves.

  14. The pure DOM example would create rows with class names of ” odd” and not just odd. This throws MSIE5/Mac and maybe some others, too.

    I doubt the value of a showdown that does not take speed into account. IMHO a good library should make it easier for the developer but also for the visitor. If a oneliner has to be computed internally and will result in a lot of nested loops and conditions that doesn’t make it better – in most cases it’ll make it slower and that affects the end user.

    A real comparison would be to show what the inner workings of each of the libraries relate to in comparison with the native DOM version (as in how many lines, overhead and rendering time are used to convert it to something the browser does understand).

    Then we could try to make it as speedy as possible. For example defining the same RegExp in each loop iteration is not needed and storing the array length in a variable instead of re-calculating it every iteration makes the loop a lot faster.

    This example also only stripes the table, a good “zebra table” solution should IMHO also highlight the current row I am hovering over. Otherwise CSS in compliant browsers can do it better and quicker (yes, CSS could also do the hovering, but not as reliable).

  15. @Chris:

    About the hovering, you could add hovering bij just adding 2 extra rules of jQuery:

    $("tr:nth-child(odd)").addClass("odd");
    
    $("tr:nth-child(odd)").hover(function(){
        $(this).addClass("odd_over");
    },function(){
        $(this).addClass("odd_out");
    });
    
    $("tr:nth-child(even)").hover(function(){
        $(this).addClass("even_over");
    },function(){
        $(this).addClass("even_out");
    });
  16. @Chris: Good to see you here!

    “The pure DOM example… throws MSIE5/Mac and maybe some others, too.”

    Good call – ironically, I’m sure none of the library example work either.

    “I doubt the value of a showdown that does not take speed into account. IMHO a good library should make it easier for the developer but also for the visitor.”

    I am of the complete opposite opinion. It is completely impossible for a library to ever match, or surpass, the speed of a pure JS/DOM solution. In fact, the only thing that a “fast solution” signifies is that the library is doing less, or is less capable. For example, Yahoo UI, Dojo, and Prototype 1.4 will all score “high” for speed, simply because they’re lacking in DOM-related features.

    The problem with testing frameworks on speed, as you propose, is that there is no “one true way” to do it. For example: Because Dojo lacks a way to select elements by tag name, it immediately receives the upper-hand. So why can’t jQuery or Mootools be allowed to not use any of their features, such that they may improve their speeds too? In the end, each library’s example is going to be fighting to be the most DOM-like, and least practical.

    Additionally, what would give me the right to write “fast” code for a particular library’s example? If I say “this” code block is the fastest way that Mochikit (for example) can do a task – and it loses, then whose fault is it? The library author’s for writing slow code – or mine, for not writing fast code with the library?

    This is why I note that all of these examples are more than fast enough for most visitors on most web pages (and if I didn’t, then I’m stating it now).

    “A real comparison would be to show what the inner workings of each of the libraries relate to in comparison with the native DOM version…”

    And what would that achieve – and who would that benefit? If all the code snippets are “fast enough” for real world use then any further analysis is simply academic. If all libraries work, but one library performs 40ms slower – does it then “lose”? 40ms isn’t even perceptible to the human eye (nor is it even, probably, renderable). So by caching .length accesses and caching RegExps, it will literally have an imperceptible benefit to the user.

    And in the end, you’re back to where you started: With code that can, equally, perform adequately. So why not just compare libraries at what they’re good at? Making code easier for a developer to write.

  17. Touting size again? I thought there was an agreement not to do anymore showdowns because they prove nothing. I’ll take the few lines from YUI over the one-liner of jQuery for the main reason that it’s faster. Also note that you can batch all your elements in the addClass method:

    YAHOO.util.Dom.addClass(HTMLElementCollection, ‘classname’);

    I’m not sure who you’re winning to jQuery by showing examples likes this. Why not just say something like “hey, jQuery has css3 Query support.”

    I could add another method to the Dom utility (like getElementsBySelector) that will essentially give me the same one-liner.

  18. My 2 cents is that I could care less about performance, to a point that is. I’ve written enough code in my day that it’s all about ease of use for me, the developer, in how little code I have to write rather than what is the fastest performing method out there. The exception is when it becomes noticeable in client performance or server load.

    If everyone wanted the fastest at all costs, we wouldn’t be using frameworks.

  19. @Dustin: “Touting size again?”
    Nope, this time I’m touting functionality. And size. ;)

    “I thought there was an agreement not to do anymore showdowns because they prove nothing.”

    See, I think they speak volumes about a library. Do they support easy JavaScript loops? How many DOM methods do I have to have memorized, in addition to a library’s functionality? Do they support selectors to make selecting elements less painful? How good are the selectors?

    Writing some form of a striped table is a very common task – many web developers have to do just that. Picking this as a simple example of what web developers actually have to do in their jobs shows you exactly the knowledge that they need to know, and the code that they need to write in order to get the job done. In addition to showing the elegance of some libraries’ solutions, this showdown shows the fact that some libraries are incapable of helping developers get common tasks done faster.

    This is a huge point – and one that can’t be seen without sitting down and trying to write some code.

    “Also note that you can batch all your elements in the addClass method”

    I really really wanted to use that bulk feature. However, I can’t add the class to every row – I have to add it to every other row, which is something that the batch function does not support.

    “I’m not sure who you’re winning to jQuery by showing examples likes this. Why not just say something like ‘hey, jQuery has css3 Query support.'”

    Because I’ve been saying it since the day that jQuery was released – 10 months ago. It doesn’t click unless you can see the actual benefits of it. The argument makes no sense – it’s like saying “why not just say that Yahoo UI can do drag and drop and be done with it.” Not showing why its better can only mean that A) its not better or B) its not worth showing. Neither of which is true – CSS3-based selectors are incredibly powerful and need to be known.

    “I could add another method to the Dom utility (like getElementsBySelector) that will essentially give me the same one-liner.”

    See, that’s fine – you can write an entire DOM selector implementation. How many other people can you name who can do the same?

    Which brings up the point: Why doesn’t Yahoo UI have a getElementsBySelector, if its so important? You can’t say that “just adding another method” is a valid argument, otherwise why can’t any other library be allowed to use mythical functions too? We’re not discussing possibilities here – the code listed above is the most concise, elegant, way to stripe table rows in all popular JavaScript libraries.

    Honestly, I hope Yahoo UI adds that function – I hope they all do. Element selectors are a huge part writing real-world JavaScript code.

  20. Pingback: links for 2006-10-21 « past is dead

  21. Ok,
    some valid points, but you still can’t really compare this to that when there’s quite a significant file size difference and the end result is a 5 liner vs a 1 liner.

    There has definitely been talks of CSS and Xpath selectors for YUI, but no clear decisions have been made. After all, Yahoo! did pick up the getElementsBySelector man himself and I no doubt have a love for faster ways to get elements in the Dom.

    All that aside (eg: DOM retrieval), YUI does many other things that are fundamentally different than jQuery. For instance with $.ajax(obj) it then becomes clear that the size of writing the procedure is the motivation behind the design (by passing in a giant object) whereas (aside from namespacing) YUI’s goal is to accomplish good readability and architecture design with Connect.asyncRequest(uri, method, callback, postData); Beyond that you get a tried and tested polling interval that will get you consistent transaction behaviors across multiple browsers, you get convenience methods like Connect.setForm(form); and file uploading that allow you to send your data without having to worry about constructing the datasets yourself and/or worry about creating iFrames (for uploading).

    In the end it’s not that I am not a fan of jQuery’s style, but rather how it’s sold.

  22. I think this example is the ideal example of jQuery’s philosphy: “Writing JavaScript code should be fun.”.

    And with this easy, simple and logical code it is indeed much fun for developers to see how easy they can achieve some things.

  23. Pingback: JQuery: Zebra Table Showdown « Rip’s Domain

  24. It’s funny, I was a Prototype guy for the longest time. Still like it in some ways. Unfortunately, the code has grown significantly since 1.3, and that’s hard on smaller sites that don’t warrant the footprint. I’d still rather use a framework than script everything manually, too.

    When I started using jQuery, naturally I wanted to do all of these things in a crazy verbose way. I initially thought, “jQuery is messed up” because I couldn’t make it do what I wanted.

    Within a week I realized that it was me who was messed up! It took time to realize that jQuery is much simpler, and I do not have to write a bunch of structured code or my own loops. Eventually, I began to embrace the one-liner. :)

    I’m not sure a framework/library war is what’s intended here (we’ve all had enough of that, right?). At best, this article is showing off the strengths of jQuery, which might look arrogant. But it’s not; it’s educational. The example here is more than useful in real-world situations, and I’m sure many will benefit from it.

  25. I completely agree with Giel here. It’s a lot easier (and more fun) to play with a one-liner to get the desired effect than to debug loops anywhere.

  26. Yo Dustin!

    I’m sure there is lots of space for improvement in the jQuery API, $.ajax is just one example. After all, jQuery is still a very young project. There is still much to learn from others…

  27. andris on said:

    BTW, the nth-child method on large tables (e.g. a 300 row report), times out on my FF (slow), so i’m using :odd

  28. Pingback: BorkWeb » The Ajax Experience: jQuery Toolkit

  29. Pingback: kchlog » Blog Archive » jQuery vs. Prototype: pointless

  30. How can I be the only one who noticed that jQuery is striping the wrong rows? From http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#adjacent-combinators “The index of the first child of an element is 1.”.

    Anyway, here’s one to add – Yahoo + yui-ext:

    getEls(“tr:nth-child(odd)”).addClass(‘odd’);

    http://www.jackslocum.com/playpen/selectors/

    yui-ext let’s you plug in whatever selector processing function you want. In this page I am using cssQuery (the default).

  31. It is interesting to see that your zebra table is starting from the second row (even) and not the first row (odd). From the CSS Spec on nth-child:

    “The index of the first child of an element is 1”.

    You can find that at: w3.org CSS Selectors

    Looking at the code you supplied:

    $(“tr:nth-child(odd)”).addClass(“odd”);

    It appears that jQuery has a bug. If nth-child started at 0, the example would be correct.

    Also, while the zebra table was, of course, an example to illustrate the one liners available in jQuery, I would hope that readers of this blog avoid using a technique like this in the real world. Why waste valuable cycles on something that should be handled in the orignal DOM? Odd and even rows don’t exactly change semantics over time.

    Otherwise, very insightful info. I was previously unware of jQuery and will definitely explore further!

  32. Am – Sorry about that, I only empty out the moderation queue every couple of days and just missed his post. It’s not really a conspiracy theory or anything, just a mistake.

  33. Animal on said:

    In fact, it can be done in one-statement (if you don’t count having to allow for fallback for non DOM-compliant browsers):

    window.onload = function()
    {
    if (document.createTreeWalker)
    {
    document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, function(el)
    {
    if (el.rowIndex & 1)
    YAHOO.util.Dom.addClass(el, “odd”);
    return NodeFilter.FILTER_SKIP;
    } , false).nextNode();
    }
    else
    {
    yuiZebraTables()
    }
    }

  34. /App_Themes/Zebra/GridView.skin

    <asp:GridView runat=”server”>
    <AlternatingRowStyle CssClass=”odd”>
    <asp:GridView>

  35. Pingback: Alan’s Kiloblog » Second System Syndrome 2.0

  36. Pingback: CSS-Based Tables: Modern Solutions | Smashing Magazine

  37. Pingback: geekgrl.net » jQuery vs Prototype - WordPress Gone Astray?

  38. Pingback: » Using MooTools and jQuery without conflict. » Joomla Components Extensions Blog

  39. Ok, this seems like a (relatively) active place as far as how to zebra a table. The table I wish to zebra is for listing upcoming gigs for a fellow musician whose website I manage. I borrowed the code for the table from Sonicbids (and I’m sure you guys can pick it apart, as it’s hardly perfect) but it works, all I want is to ease the entering of new gigs. Less typing for me = joy. Here’s my dilemma, however: each tr has a hidden tr beneath it, which is displayed or hidden again upon being clicked, revealing the details of that particular gig. It’s hardly elegant in it’s coding (I’m no javascript guru; like I said, I borrowed the code, and if any of you can help me figure out a better way to build the page, sweet!), but again, it works. I’ve got an odd class set up already, problem is, it’s more like every other TWO rows should be the odd or even (plain) class (i.e. odd row, hidden odd row, even row, hidden even row, etc.) Here’s the URL of the page in question:

    http://www.marcusrezak.com/shows.html

    Anybody have any ideas?

    And yes I know this is not the best place to be asking this, but it’s the only thing I came up with after trying to implement Dustin Diaz’s zebra method, then searching for some help on how to modify it (and yes, I tried to contact Dustin directly, but I cannot seem to find his email address or a contact form on his site; his about page, sitemap & contact links after performing a search lead to the main page again.)

  40. Pingback: CommaDot.com » Blog Archive » js.commadot.com

  41. Pingback: SachinKRaj - get something useful from web CSS-based tables - get into stylish web 2 «