Christian and I collaborated the other day on a new feature for the excellent jQuery plugin, TableSorter. I can only take credit for the idea as Christian made the magic happen. This addition makes it now possible to sort any content, whether it be a form field, inside markup, etc.
The Situation
For simple data or markup, TableSorter can normally just grab the data and sort but there are cases where that won’t work. Take for example a table with a form field in it. Tables in forms??? Yes, validator-weenies, I have no problem putting a form into a table for tabular editing. The problem is that TableSorter doesn’t know the value of the form element so sorting fails. Furthermore, in my case, I wanted to sort on the display value of a select box since the value was a 35-character UUID.
My “tableform” looks like this:
I wanted to be able to sort on the select boxes, make changes, and sort again.
The Change
I am a relatively clueless Javascript hacker so I asked Christian if he thought a custom extractor or parser was the best way to go about getting at the value and he suggested parsers, because they’re more powerful. Then I asked how you get a reference to the content of the cell and he said, “Oh, hold on…” A few minutes later, he had a new revision checked into subversion that changed the API from:
is: function(s) {
// returning false prevents auto-detection; we manually assign this
return false;
}
format: function(s) {
s += ', ' + new Date().getYear();
return $.tablesorter.formatFloat((new Date(s)).getTime());
}
});
where the is() and format() functions just return the HTML content of the node to accepting multiple arguments that include a reference to the table and the table cell like so:
is: function(s, table, node) {
// returning false prevents auto-detection; we manually assign this
return false;
}
format: function(s, table, node) {
s += ', ' + new Date().getYear();
return $.tablesorter.formatFloat((new Date(s)).getTime());
}
With this API change, I could now write a parser to pull out the display value from the select box with just a little jQuery sugar:
$.tablesorter.addParser({
id: 'selectBox',
is: function(s, table, node) {
if (!node.childNodes[0].tagName) return false;
return (node.childNodes[0].tagName == 'SELECT') ? true : false;
},
format: function(s, table, node) {
// use new node argument to get the display value of the select box
return $(node).find("option:selected").text();
},
type: 'text'
});
$("#tblAssignments").tablesorter({
,headers: { 3: { sorter: 'selectBox' }
,4: { sorter: 'selectBox' }
}
});
As you can imagine, with a jQuery object available, there isn’t anything you can’t sort in a table. Very cool!
Update
For efficiency, TableSorter caches the formatted data to make sorting quick. But when value of a form field changes, TableSorter needs to be told about it with the update() function:
$("table#tblAssignments select").change(
function()
{
$(this).parents("table").trigger("update");
}
);
Now every time you change a select box, the data is refreshed. In a smallish table, this only takes 10-30ms. I haven’t tested yet on a large dataset but it’s sufficiently fast. If performance becomes a problem, we could find a way to update a single item in the cache.
Grouping/Collapsing
I also wanted to be able to add grouping and running tallies for groups using TableSorter and came across a groups widget on Google code. The comments and variable names are all in some Slavic language meaning I can’t make head nor tail of it, but the good news is that this plugin does work! I’ve updated it a bit to always show a running tally and so forth so contact me if you’d like my more-hacky version. The project description is pretty clear.
This screenshot shows several of the groups collapsed into a thin bar with a tally of how many items are in each group. Thanks goes out to Christian for the new feature and for revving the version number against his will!
Alex said:
on September 21, 2008 at 4:12 pm
I’m having problems getting my table to update after I made a change with a select box.
Where should that code be added?
Also, if we don’t want to update until after a successful ajax call, for example, shouldn’t it be possible to use:
$(‘#myTable’).trigger(“update”);
Any help would be greatly appreciated.
brian said:
on September 22, 2008 at 3:15 pm
@Alex, the example above shows the idea; you want to put it onChange; this could go into your on ready:
$("#select_box_id").change(
function()
{
$(this).parents("table").trigger("update");
}
);
Throw an alert in there if you want to debug…