Note: This site might seem inactive… That’s because it is. Don’t worry though, I’m still coding webpages and stuff! If you’re interested, I suggest you get a translator and head over to Qiwi; or you could just check the latest site we’ve been working on: AD Delhaize Lebbeke. Enjoy!
Highlighting alternate sorttable rows
Or, a way how to highlight alternate table rows without breaking the table’s JavaScript-wise sortability. Or the other way around. Whatever.
The scripts
Well, it should be obvious what they do and how they work, but hey.
sortTable()is an interesting piece of JS goodness that — when enabled — searches the document forTABLEelements, then adding clickable headers that sort the table by the clicked column. In human language: it makes your tables sortable. The original script only does this forTABLEs who have aCLASSof
, but in the quest of avoiding additional markup, I hacked that shit away.sorttable- That other function,
colorTableRows(), runs through all table rows, checks if they’re odd or even, and then applies aCLASSattribute with a value of
to the odd ones. That way, alternate table rows can be easily styled through CSS.alternate
Needless to say, both these functions kick butt and add usability to your tables.
The problem
When combining these two scripts, the following happens. As soon as the document containing the table is loaded, the colorTableRows() function performs its task. After this is done, sortTable() initializes, adding links to the table headers (i.e. making them clickable). So far, all is well. Until the user decides to click one of those THs in order to sort the table. Try it out yourself, you should notice utter crappiness as for the highlighted table rows.
The solution
…is pretty simple actually, once you realize that since the sorting reorders the rows, we have to reset the highlights after the actual sorting. This translates into a couple of tweaks to the code, of which I’ll spare you the details. Here’s the full source code to the perfect collaboration of these scripts.
/* http://brothercake.com/site/resources/scripts/domready/
**************************************************************************/
function domFunction(f, a) {
var n = 0;
var t = setInterval(function() {
var c = true;
n++;
if(typeof document.getElementsByTagName != 'undefined' && (document.getElementsByTagName('body')[0] != null || document.body != null)) {
c = false;
if(typeof a == 'object') {
for(var i in a) {
if((a[i] == 'id' && document.getElementById(i) == null) || (a[i] == 'tag' && document.getElementsByTagName(i).length < 1)) {
c = true;
break;
}
}
}
if(!c) { f(); clearInterval(t); }
}
if(n >= 60) {
clearInterval(t);
}
}, 250);
};
/* http://kryogenix.org/code/browser/sorttable/
**************************************************************************/
var sci;
function sortTable() {
if (!document.getElementsByTagName) return;
tbls = document.getElementsByTagName('table');
for (ti=0; ti<tbls.length; ti++) {
thisTbl = tbls[ti];
st_makeSortable(thisTbl);
}
}
function st_makeSortable(table) {
if (table.rows && table.rows.length > 0) {
var firstRow = table.rows[0];
}
if (!firstRow) return;
for (var i=0; i<firstRow.cells.length; i++) {
var cell = firstRow.cells[i];
var txt = st_getInnerText(cell);
cell.innerHTML = '<a href="" class="sortheader" onclick="st_resortTable(this);return false;">'+txt+'<span class="sortarrow"></span></a>';
}
}
function st_getInnerText(el) {
if (typeof el == 'string') return el;
if (typeof el == 'undefined') return el;
if (el.innerText) return el.innerText;
var str = '';
var cs = el.childNodes;
var l = cs.length;
for (var i = 0; i < l; i++) {
switch (cs[i].nodeType) {
case 1:
str += st_getInnerText(cs[i]);
break;
case 3:
str += cs[i].nodeValue;
break;
}
}
return str;
}
function st_resortTable(lnk) {
var span;
for (var ci=0; ci<lnk.childNodes.length; ci++) {
if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
}
var spantext = st_getInnerText(span);
var td = lnk.parentNode;
var column = td.cellIndex;
var table = st_getParent(td, 'TABLE');
if (table.rows.length <= 1) return;
var notDate = 0;
var notCurrency = 0;
var notNumerical = 0;
for (var itmc=1; itmc<table.rows.length; itmc++) {
var itm = st_getInnerText(table.rows[itmc].cells[column]);
if (!(itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/) || (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/))))
notDate++;
if (!(itm.match(/^[?£$]/)))
notCurrency++;
if (!(itm.match(/^[\+-]?[\d\.,]+$/)))
notNumerical++;
}
switch (0) {
case notDate: sortfn = st_sortDate; break;
case notCurrency: sortfn = st_sortCurrency; break;
case notNumerical: sortfn = st_sortNumeric; break;
default: sortfn = st_sortCaseInsensitive;
}
sci = column;
var firstRow = new Array();
var newRows = new Array();
for (i=0; i<table.rows[0].length; i++) { firstRow[i] = table.rows[0][i]; }
for (j=1; j<table.rows.length; j++) { newRows[j-1] = table.rows[j]; }
newRows.sort(sortfn);
if (span.getAttribute('sortdir') == 'down') {
ARROW = ' ?';
newRows.reverse();
span.setAttribute('sortdir', 'up');
} else {
ARROW = ' ?';
span.setAttribute('sortdir', 'down');
}
for (i=0; i<newRows.length; i++) { if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) table.tBodies[0].appendChild(newRows[i]); }
for (i=0; i<newRows.length; i++) { if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) table.tBodies[0].appendChild(newRows[i]); }
var allspans = document.getElementsByTagName('span');
for (var ci=0; ci<allspans.length; ci++) {
if (allspans[ci].className == 'sortarrow') {
if (st_getParent(allspans[ci], 'table') == st_getParent(lnk, 'table')) {
allspans[ci].innerHTML = '';
}
}
}
span.innerHTML = ARROW;
colorTableRows();
}
function st_getParent(el, pTagName) {
if (el == null) return null;
else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())
return el;
else
return st_getParent(el.parentNode, pTagName);
}
function st_sortDate(a, b) {
aa = st_getInnerText(a.cells[sci]);
bb = st_getInnerText(b.cells[sci]);
if (aa.length == 10) {
dt1 = aa.substr(6, 4)+aa.substr(3, 2)+aa.substr(0, 2);
} else {
yr = aa.substr(6, 2);
if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
dt1 = yr+aa.substr(3, 2)+aa.substr(0, 2);
}
if (bb.length == 10) {
dt2 = bb.substr(6, 4)+bb.substr(3, 2)+bb.substr(0, 2);
} else {
yr = bb.substr(6, 2);
if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
dt2 = yr+bb.substr(3, 2)+bb.substr(0, 2);
}
if (dt1==dt2) return 0;
if (dt1<dt2) return -1;
return 1;
}
function st_sortCurrency(a, b) {
aa = st_getInnerText(a.cells[sci]).replace(/[^0-9.]/g, '');
bb = st_getInnerText(b.cells[sci]).replace(/[^0-9.]/g, '');
return parseFloat(aa) - parseFloat(bb);
}
function st_sortNumeric(a, b) {
aa = parseFloat(st_getInnerText(a.cells[sci]));
if (isNaN(aa)) aa = 0;
bb = parseFloat(st_getInnerText(b.cells[sci]));
if (isNaN(bb)) bb = 0;
return aa-bb;
}
function st_sortCaseInsensitive(a, b) {
aa = st_getInnerText(a.cells[sci]).toLowerCase();
bb = st_getInnerText(b.cells[sci]).toLowerCase();
if (aa==bb) return 0;
if (aa<bb) return -1;
return 1;
}
function st_sortDefault(a, b) {
aa = st_getInnerText(a.cells[sci]);
bb = st_getInnerText(b.cells[sci]);
if (aa==bb) return 0;
if (aa<bb) return -1;
return 1;
}
/* http://ktk.xs4all.nl/stuff/javascript/table-row-alternate/
**************************************************************************/
function colorTableRows() {
if (document.getElementsByTagName) {
var tables = document.getElementsByTagName('table');
for (var i = 0; i < tables.length; i++) {
var trs = tables[i].getElementsByTagName('tr');
for (var j = 1; j < trs.length; j++) {
trs[j].className = (j % 2 == 0 ? '' : 'alternate');
}
}
}
}
/* Load the scripts
**************************************************************************/
var foobar = new domFunction(function() {
sortTable();
colorTableRows();
});
Note: I’m using the domFunction helper script to load the other scripts; you can of course use Scott Andrew’s addEvent handler instead if you smartassly prefer so.
In order to make it more easy for you to implement this script on your site, I’ll share some basic table-styling CSS below. Feel free to Steal This Code™ and/or make it your own!
table {
width: 100%;
border: 1px solid #edf3fe;
empty-cells: show;
}
th {
text-align: center;
font-weight: bold;
background: #3d80df;
padding: 1em;
color: #fff; /* for THs that won't contain links */
}
/* A little bit of Paul Fitts */
th a {
display: block;
width: 100%;
padding: 1em;
text-decoration: none;
color: #cef;
margin: -1em 0 -1em -1em;
background: #3d80df;
}
th a:hover {
background: #f3008a;
}
/* If the browser supports CSS3,
we don't actually need colorTableRows() */
tr:nth-child(odd) {
background: #edf3fe;
}
/* But we'll use it anyway because we're so cool */
tr.alternate {
background: #edf3fe;
}
Conclusion
Given the right treatment, sortTable() and colorTableRows() go together so symbiotically.
Thanks
Props to Stuart and Krijn for the nifty scripts, and kudos to João for fixing the bug he discovered himself. What a guy.
Comments (20)
Listed below are the responses for this entry.
Trackbacks & Pingbacks (1)
Listed below are resources on the web that mention this article.
-
- If..Else Log: Highlighted, sortable tables:
Highlighted, sortable tables
Mathias shows how to create a dynamically sortable, alternately highlighted table. […]- Trackback made on September 3rd, 2005 @ 12:37 pm
Hey, I’ve got a new referrer \o/ Clever usage of the modulo operator for alternating rows there BTW ;)
Very, very nice! I’ve been quite impressed with the
sortTable()function, but always felt something was missing. Looks like you filled that void :-)Sorting by name isn’t working that well — could it be because of that numerical title, 1982?
Here’s what I get when trying to sort by name (I’ll use the numbers to avoid cluttering your comments):
TH: name)TH: name ?)TH: name ?)TH: name ?)TH: name ?)… … and so on … …
Now that’s some funky shit! Looks like you’ve discovered a bug in
sortTable(). I think 1982 is confusing thest_resortTable()function, especially the part in which it attempts to determine the column type (date/currency/numeric). Any ideas?I’ll inform Stuart. After all it’s his original code; perhaps he knows of a proper solution.
Well, my first thought, given your comment, was that the script was using only one item to . Looking at the code gave me the certainty of it.
So, the thing goes like this: the script is using the wrong logic to determine the column type. A numerical column is not , but . Same goes for date and currency, being “text” the default case.
I’ll see if I can come up with a correction and, if so, I could e-mail it to you (I see you don’t have your address published, so, if you also prefer e-mail as the mean, please send me one so I can send you the goods).
I noticed the same faulty behaviour on the column. The original script doesn’t treat a formatted number with grouped thousands (in this case ) as a number.
Thanks for the email, I can’t believe you actually solved this problem in, like, five minutes. Respect! I updated the online demo, as well as the script’s code displayed in this post. (The example page however is left untouched, as after all that should contain the original code.)
Just for the record: my email address is in fact published; it can be found on the contact page.
Very nice, Mathias, I love it!
João Craveiro is entirely correct; the script should, in an ideal world, determine the type of a column by assessing all values in it, not just the top one. The major reason that it doesn’t is that it takes rather a long time, in large tables, to match a regexp against every value in the column. Certainly if you’re using shorter tables it would be a good fix to change to João’s solution.
Perhaps it would be a good idea to not check one column value exclusively (as with the original
sortTable()), not check all values (as with João’s fix), but for instance check three: the first value, the last value, and one somewhere in the middle of the column. If most of those three are dates, then the column type is most probably ; if most are currencies, then that column should probably be treated as such; else, one can suppose the column contains numeric data.Yes, despite being, by far, the most accurate (but still not 100%) way of determining how a column should be sorted, checking all rows as a
O(n)complexity: the bigger the table, the longer it takes. Checking 3 items, or even a fraction of the rows that’s proportioal to their amount (e.g., if the table had like 42 rows, one could check 4 or 5 rows instead of just 3) is a fair solution, but leaves the correct sorting of the column to luck.Oh, and when you say and , it should be all of the checked items.
Wasn’t there a problem involving a song title being 1982, which of course looks like a number, but isn’t in the context of this very table?
I should’ve probably said ; I guess that’s more clear.
Precisely. Assume you have a column like that one, but with its first, last and middle values (in the present ordering) solely numerical, while the rest were normal text titles. The column should be sorted as text, but will be as numbers, because most (indeed, all) of the considered values are numerical. In fact, that’s what occured before: happened to be the first value in some orderings, forcing the column to be sorted as numbers.
Alright, so that was stupid. I took for granted special values like that would always be either on top or on the bottom of the list, shamelessly forgetting about the initial state of the loaded page. Doh!
I use a table sorting thing as well and have run into similiar issues.
The issue of the comma delimited numbers was problematic for me as well. I tried string replacing commas, but that didn’t work too well. What I ended up doing was assigning a
classtag to eachTDwith the unformatted number and sorting on that if it existed.As for the wrong sort order being deteremined, you could do much the same thing. Simple add a
class="date"orclass="string"etc to theTH.Also, a good improvement to add is
class="nosort"for columns that can’t/shouldn’t be sorted.Actually,
CLASSis an attribute (there is no such thing as<class/>, right?). And that solution doesn’t sound very semantic to me…That also is a lot of unnecessary/additional work/markup.
In fact, that’s not very sem—okay. I can see it improves usability though.
What is exactly better than
sortTableRows ();from Robbert Broersma?The problem with this kind of approach is that every script that manipulates tables has to be rewritten to not break in this manner. This isn’t always possible, for example page authors can’t rewrite userjs scripts, and userjs script authors can’t write generic scripts that preserve styles such as this.
The right way of doing it is to hook
colo[u]rTableRows()up to the table’s DOMSubtreeModified event. That way, the document tree modification and the style update is decoupled, so you don’t have to code them to work together. Unfortunately, the only rendering engine that supports that event right now is KHTML. Grumble…I like it a lot!!!
But then you always think something is missing:
I solved the first one with a new bit of CSS
…and a modification to
colorTableRows():The second issue of the resetting I solved only for one table on a page (because that is all I needed). I added one global variable after:
…and two new functions:
Then I call
get_original()insortTable():On my HTML page I added the following button:
Pressing it will restore the original table sorting, get rid of the arrow and of the background coloring.
What a wonderful piece of javascript….very helpful !
I would like to use it for my own GPL package with the full credit if you are agree.
Really Nice ! :)
I tried replacing the original sort table v2 with this new one and it sorts too slow. The old one sorts my giant table as fast as I can click, this new one lags a bit even with
colorTableRows()removed…