Thursday, June 11, 2009

Collapsible tables: handling ranges of elments with jQuery

In the past week or so I've answered a couple of questions on StackOverflow about how to handle operations ranges of elements before/after a particular element. One recent question had to do with collapsible tables and I thought I'd share my answer to that question as general technique for this type of operation.

The basic idea is that you have a series of elements that you want to do something with within a larger series of similar elements. For example, you have a table consisting of some rows that are "headers" and some that are "row". You want to be able to operate on the sub-range without applying the same operations to the entire range or elements outside the subrange.

One way to do this is to use classes to mark your elements with context. Then use the each method, with prevAll and nextAll operations on the given element to iterate through the range of elements, performing the desired operation until you come to an element that has a class (context) that marks the end of the range.

For example, consider the following table:

<table>
<tr class='header'><th>header 1</th></tr>
<tr class='row'><td>row 1-1</td></tr>
<tr class='row'><td>row 1-2</td></tr>
<tr class='row'><td>row 1-3</td></tr>
<tr class='header'><th>header 2</th></tr>
<tr class='row'><td>row 2-1</td></tr>
<tr class='row'><td>row 2-2</td></tr>
<tr class='row'><td>row 2-3</td></tr>
<tr class='header'><th>header 3</th></tr>
<tr class='row'><td>row 3-1</td></tr>
<tr class='row'><td>row 3-2</td></tr>
<tr class='row'><td>row 3-3</td></tr>
</table>


When you click on a row, you want all the rows in the local range (between the headers) to collapse. When you click on a header (for a collapsed set of rows) you want it to re-expand.

To accomplish this, create a click handler for the rows with class row, that hides itself and both the previous and next rows until it encounters a row with class header


$('table tr.row').click( function() {
$(this).hide();
$(this).prevAll('tr').each( function() {
if ($(this).hasClass('header')) {
return false;
}
$(this).hide();
});
$(this).nextAll('tr').each( function() {
if ($(this).hasClass('header')) {
return false;
}
$(this).hide();
});
});


And to re-expand the collapsed rows, create a click handler for the rows with class header, that iterates through the next row siblings of the header, showing each row until it encounters another row with class header


$('table tr.header').click( function() {
$(this).nextAll('tr').each( function() {
if ($(this).hasClass('header')) {
return false;
}
$(this).show();
});
});