I am looking for an efficient way to expand/collapse hierarchical table rows using jQuery. The problem is, that the expand and collapse functionality differs.
The table html in your post is not valid (tr enclosed by td). On the properly structured table, this code works.
$("tr.level_0").live("click", function () {
$(this).nextUntil(".level_0").toggle();
});
I think you're gonna need a little some more code so you can handle clicks on all the rows in both closed and open states.
I added a class switch to indicate when a row is open, and treat clicks differently based on it's state. Both collapse
and expand
have while loops that walk through rows, starting with the one immediately after the clicked row, and they stop when they get to rows of the same level. This logic should work for any level, not just 0. Also, the while
loop could probably be cleaner using nextUntil
logic with a fancy selector - I wasn't familiar with that jQuery method until seeing Jules answer - very slick!
ALso, for keeping this example code simpler, I treated your level class naming system as HTML data attributes, so a given row looks like this: <tr data-level="0" id="10">
. You could replace calls to .data
with code to parse your class names.
var $rows = $('#mytable tr');
$rows.live('click', function() {
$(this).toggleClass('open');
if ($this.hasClass('open')){
collapse($(this));
} else {
expand($this);
}
}
function collapse ($row) {
$row.removeClass('open');
var rowIndex = $rows.index($row);
var siblingOrAncestorRowFound = false;
while (!siblingOrAncestorRowFound){
var $nextRow = $rows.eq(rowIndex + 1);
if ($nextRow.level > $row.level){
$nextRow.hide().removeClass('open');
rowIndex++;
} else {
siblingOrAncestorRowFound = true;
}
}
}
function expand ($row) {
$row.addClass('open')
var rowIndex = $rows.index($row);
var siblingOrAncestorRowFound = false;
while (!siblingOrAncestorRowFound){
var $nextRow = $rows.eq(rowIndex + 1);
if ($nextRow.level > $row.level){
// only show rows exactly one level below
if ($nextRow.level == $row.level + 1){
$nextRow.show();
}
rowIndex++;
} else {
siblingOrAncestorRowFound = true;
}
}
}
(This isn't tested - sorry gotta hit the sack!)
I've created a version for multiple levels of hierarchy as an answer to another question.
The jQuery for your table would be:
var treeTable = {
parentClassPrefix : '',
collapsedClass : 'collapsed',
init : function(parentClassPrefix) {
this.parentClassPrefix = parentClassPrefix;
$('table').on('click', 'tr', function () {
treeTable.toggleRowChildren($(this));
});
},
toggleRowChildren : function(parentRow) {
var childClass = this.parentClassPrefix+parentRow.attr('id');
var childrenRows = $('tr', parentRow.parent()).filter('.'+childClass);
childrenRows.toggle();
childrenRows.each(function(){
if (!$(this).hasClass(treeTable.collapsedClass)) {
treeTable.toggleRowChildren($(this));
}
});
parentRow.toggleClass(this.collapsedClass);
}
};
treeTable.init('parent_');
See this JSFiddle for it working.
I have a slightly different table structure, which involves specifying parents as well as children so that the searching can be more efficient.
I've then also made a jQuery hierarchical table row toggling Gist from it and converted it into objectified javascript with optimisations. The optimisations come from http://24ways.org/2011/your-jquery-now-with-less-suck/.
Specifically:
Event delegation on the table rather than on the rows.
$('table').on('click', 'tr.'+treeTable.parentClass, function () {
treeTable.toggleRowChildren($(this));
});
Cache the children selection.
Use the faster element selector followed by a slower filter on the .childClass
var childrenRows = $('tr', parentRow.parent()).filter('.'+childClass);