Stripe your tables the OO way

Apr 02 2006

Update 16/05: After several requests, I have updated this script to use a more reliable method of swapping the class names in and out. The new addClassName and removeClassName functions can be found on the demo page (or in the original Particletree article I swiped them from).

A little over two years ago there was a brief flurry of interest in the ‘Zebra Tables’ technique after David Miller published an article of the same name on A List Apart. Unfortunately his Javascript has not stood the test of time and now looks pretty antiquated; the improvements made by Jop de Klein were better, but when I needed to implement a zebra table on a recent project it seemed much easier to simply write my own version.

It’s not perfect (it assumes that no class names are being used on the table rows apart from for the striping), but it does the job quite well:

  1. var ZebraTable = {
  2. bgcolor: '',
  3. classname: '',
  4. stripe: function(el) {
  5. if (!$(el)) return;
  6. var rows = $(el).getElementsByTagName('tr');
  7. for (var i=1,len=rows.length;i<len;i++) {
  8. if (i % 2 == 0) rows[i].className = 'alt';
  9. Event.add(rows[i],'mouseover',function() { ZebraTable.mouseover(this); });
  10. Event.add(rows[i],'mouseout',function() { ZebraTable.mouseout(this); });
  11. }
  12. },
  13. mouseover: function(row) {
  14. this.bgcolor = row.style.backgroundColor;
  15. this.classname = row.className;
  16. addClassName(row,'over');
  17. },
  18. mouseout: function(row) {
  19. removeClassName(row,'over');
  20. addClassName(row,this.classname);
  21. row.style.backgroundColor = this.bgcolor;
  22. }
  23. }
  24.  
  25. window.onload = function() {
  26. ZebraTable.stripe('mytable');
  27. }
  28. Download this code: /code/zebra.txt

The ‘stripe’ method runs through each row in the specified table and applies the ‘alt’ class, then adds onmouseover and onmouseout functions; these use local variables to keep track of the class name and background colour as they are swapped.

In your CSS you’ll need to specify a default style for table rows, plus tr.alt and tr.over classes. Here’s a simple demo, which also includes the other functions you’ll need (an Event registration object and Prototype’s $ function).

Filed under: DOM, CSS.

Digg this article

Bookmark this article with del.icio.us

Previously: Equal height boxes with Javascript

Next: Celebrating CSS Naked Day!


Comments

David
2942 days ago
Very nice – thank you. I’ve been using the original method on a few sites for a while … since I don’t need 2 classes per row, I think I’ll be upgrading to this method!

Thanks again.
#1
Matthew Pennell
2941 days ago
Glad you liked it, David – I may add a better CSS class changing method if I get time; the more bulletproof these things are the better!
#2
Andy Saxton
2941 days ago
You seem to be loving the DOM scripting at the moment which is no bad thing. I am at the newbie stage at the moment as I am swapping from ASP to PHP and reading Jeremy’s book.

In a few months I may be able to comment and know what I am talking about!
#3
Matthew Pennell
2941 days ago
Andy: Yeah, it just seems that there’s a lot more fun stuff to experiment with in Javascript than in CSS.
#4
Alex
2931 days ago
Iíll definitely look into that. Thanks for the tip.
#5
Alex
2931 days ago
I’m also very like your flower:)
#6
gunt
2906 days ago
Bug on Opera 8.54 webbrowser.
#7
Stuart
2906 days ago
Why not just increment by 2 then you don’t need the modulo?

(A similar thing was mentioned by Jeremy Keith at SXSW someone sent him a correction to his stripey tables function.)
#8
Phunky
2906 days ago
Looks nice, i shall have to have a play with this myself :)
#9
Matthew Pennell
2906 days ago
gunt: Do you want to be a bit more specific?

Stuart: Yes, I could just increment by 2; the tables I was using it on were so small that I wasn’t really bothered about processing overhead (if indeed modulo has any compared to the for loop).
#10
Koen
2906 days ago
There is another point where you can optimise this piece of code. In lines 5 and 6 you’re calling $(el) two times, which is unnecessary. By storing it in a variabele you save one call of $().

But it sure is nice :)
#11
romain
2906 days ago
Be carful of memory leak problem with a lot of line… Add an evet to each rows use many ressources.
#12
dreamer
2906 days ago
Admittedly, this is short and slim. But unfortunately your zebra script shares the same shortcomings of all the similar scripts out there.

First, it uses getElementsByTagName which doesn’t take the nesting depth in respect. Thus it gets nested tables wrong. Admittedly, nested tables are uncommon, but they still happen for very special reasons and I’ve had this problem with some of my customers. This can be solved by traversing the childNode array of the TBODY child element. Which takes a little more effort than a simple getElementsByTagName, of course.

Furthermore, your function doesn’t add to but overwrite any existing class on the TRs it meets. This can be solved with a simple addition of the class to the existing className or with a more elaborate function which splits the class names and adds to an array before rejoining them.

I don’t mean to disrespect your effort but rather point out the difficulties that don’t show on first sight but may bite you in the behind at some point.
#13
chromice
2906 days ago
This piece of code does not work well in Opera 8.54. It looks like an entire chain of rows partially fail to change their bgcolor back from the hover state (only the top padding and the ‘body’ of a row does). It’s really hard to describe.
In Opera 9 beta the glitch’s gone, but the 2nd row has the same style as the 1st and 3rd.
#14
Daniel
2906 days ago
Pretty slick, though shouldn’t this be tagged ‘the JSON way’ instead? ;D

Also, whats with the window.onload at the end? You’re bound to make your neat little buttoned-up Event object cry. Think of the children!

Event.add(window, ‘load’, function(){ZebraTable.stripe(‘mytable’);};

All silliness aside, well-done (a month after the fact :D)
#15
Matthew Pennell
2906 days ago
dreamer: Good point about nested tables – that wasn’t an issue for the situation in which I wrote it, but if the function was to be made more generic, that would certainly need to be taken into account.

As I noted in the article and reply #2 above, I know that it doesn’t handle rows with existing class names.

Daniel: True – I threw that in so that those without much JS experience would recognise what was happening with the load event, but you’re right, I should use the Event object really!
#16
Matthew Pennell
2906 days ago
That’s certainly a weird issue in Opera 8. The background colours get messed up in the initial load and the mouseover/out events, but if you Alt+Tab to another window and back again, they sort themselves out!
#17
nitooe
2905 days ago
except for the classname issue there would be a multiple classname issue as well besides that,... it is wrong to look up the table by ID instead of with a custom function getClassname since one can not have recurring ID’s on a page so you limmit your self to one stripe table per page.
#18
Matthew Pennell
2905 days ago
nitooe: Obviously if you wanted to apply the striping to multiple tables you would have a separate line for each one; if you wanted to, you could easily change it to use a class name instead.
#19
Dustin Diaz
2905 days ago
Not to put a damper on any of this, but this isn’t Object Oriented. This simply just takes advantage of the object literal (which is fine).

I will also echo the comment on overriding the className. You should look into fixing it so that it doesn’t override existing classnames. Since you’re already using prototype, you may just want to take advantage of the Element object which has ‘addClass’ and ‘removeClass’.

If you were doing all this with YUI libriries, you could fork out only 4k (Event Util and Dom Util) and shorten up the code even more, not to mention that you wouldn’t have to use anonymous functions in the listener call – since the scope of ‘this’ is corrected for you in the callback function.

Overall, not bad. If it works for you, that’s all the matters.
#20
Matthew Pennell
2905 days ago
Hi Dustin – yeah, I know it’s not really OO, but it sounded better than ”...the OL Way”!

Enough people have commented on the limitations of the class-swap that I’ll change it for a more robust version (I’m not using all of Prototype, just the $ function).

Thanks for your comments – it was your (similarly confusingly named) article that got me started on writing better Javascript, so thanks for the inspiration!
#21
Joost de Valk
2905 days ago
I’ve actually done an inobtrusive javascript that does this, and can handle cases where you already have a class name as well: http://www.joostdevalk.nl/code/alternating-table-rows/
#22
Chris Marks
2904 days ago
I’ve create a lot of report pages that use rowspan’s. The row highlighting that I have done in the past needed a tbody tag wrapped around the rows to get the hightlight to work on the spanning rows. So I have had to create different versions of the function. Is there a way to use one script to do this?
#23
Matthew Pennell
2904 days ago
Chris: My function works with rowspanned table cells, as the mouseover effect is applied to each table row.
#24
Dustin Diaz
2904 days ago
Haha. Yea. put the blame on me.

Fair enough. My contention was only that it was still “Object Notation” – and since it was JavaScript, I figured it was fair to call it JSON, however not JSON the data exchange format.

Anyway, yea, if you don’t want to get into the nitty gritties of using an entire library to do this, simply just append the classname using the += ’ myclassName’

and then when you’re removing it.. just using the replace method on the string object. Eg:

el.className = el.className.toString().replace(’ alt’,’’);

That should do the trick
#25
Matthew Pennell
2904 days ago
I’d probably write a simple regex instead of just appending the class name with a space.
#26
wesley
2892 days ago
Check out this tutorial http://15daysofjquery.com/examples/zebra/index.html to see how to do it the Jquery way (5 lines!)
#27
Matthew Pennell
2892 days ago
wesley: It’s an interesting project Jack’s doing, but you’ve got to ask yourself if all you are doing is striping one table, is it really worth importing a 30KB framework file?
#28
wesley
2891 days ago
If that’s all your using it for, no, but most people will use this on heavy javascript/ajax type sites, in which case it’s much more lightweight than any of the alternatives (prototype, yahoo)

And compressed it’s only about 10kb. (Dean Edwards packer)
#29
Jack
2888 days ago
And the other point of what I am doing is showing that if someone doesn’t have the skills of a Mathew Pennell – they can still get the same effect if they’re willing to import the jQuery library.

Thank you for the inspiration on the table striping.
#30
Matthew Pennell
2888 days ago
Hi Jack – I like what you’re doing, although I think there’s a debate to be had about whether frameworks can encourage people to use scripting without really understanding what they are doing with it.
#31
Jack
2887 days ago
Mathew, that’s a great point and it would be difficult for me to decisively ‘win’ that debate. But I suppose the same criticism could be leveled against most tools that make a task easier. I think in general there is some backlash on the web against overuse of AJAX, for example.

By the way, hats off to you on the design of this site. Very nice.
#32
Craig
2875 days ago
This is a great script. However, is there a fairly simple way to exclude table data cells within the footer (tfoot) from the hover effect in IE/Win?
#33
Craig
2875 days ago
Apologies for the double post, but the following seems to work. I’ve no idea if this is ‘good’ JavaScript, seeing as my scripting skills aren’t exactly top-notch, but it appears to work fine in IE, Safari, Firefox and Opera.

if(rows[i].parentNode.nodeName==’TBODY’){

if (i % 2 == 0) rows[i].className = ‘alt’;
Event.add(rows[i],’mouseover’,function() { ZebraTable.mouseover(this); });
Event.add(rows[i],’mouseout’,function() { ZebraTable.mouseout(this); });
}
#34
Matthew Pennell
2874 days ago
Craig: That would work, but requires an additional check for every row.

A better approach would be to restrict the scope of the loop in the first place:

var rows = $(el).getElementsByTagName(‘tbody’)[0].getElementsByTagName(‘tr’);
#35
Kevin
2868 days ago
Mathew, you mentioned:
‘Obviously if you wanted to apply the striping to multiple tables you would have a separate line for each one; if you wanted to, you could easily change it to use a class name instead.’

How do you ‘simply change it to a class name’?

I need to be able to have multiple tables striped on multiple pages, and would like to have it just simply work off of a .class name.
#36
Matthew Pennell
2867 days ago
Kevin: To change it to look for multiple tables having a particular class name, I would change the line:

var rows = $(el).getElementsByTagName(Ďtrí);

And instead grab an array of all the tables with the class name and then iterate through them:

var tables = document.getElementsByClassName(myclass);
for (var i=0,len=tables.length;i<len;i++) {
var rows = tables[i].getElementsByTagName('tr');

Obviously you then have to close that loop after all the mouseover assigment code.
#37
Jesse
2863 days ago
Hey Matt,

Nice little script. I wrote something myself based off of “Better
Zebra Tables” (http://validweb.nl/artikelen/javascript/better-zebra-tables/)
which has a bunch of the features people have been leaving comments
requesting for (highlighting, disabling).

It’s a decent size file, but it keeps your html nice and clean. Feel
free to poke around it!
http://www.eklekt.com/widgets/zebras/index.php
#38