简体   繁体   中英

Expand and Collapse of 3 level hierarchical rows in html table

I have some hierarchical data in a table structure as so: (example 1)

A 
  A.1 
    A.1.1 
    A.1.2 
  A.2 
    A.2.1
    A.2.2
B
  B.1
    B.1.1
    B.1.2
  B.2
    B.2.1
    B.2.2

I would like to be able to expand and collapse within each level, so example, If I click on A.2, it's children should collapse: (example 2)

A 
  A.1 
    A.1.1 
    A.1.2 
  A.2 
B
  B.1
    B.1.1
    B.1.2
  B.2
    B.2.1
    B.2.2

And if I click A, all of its children should collapse: (example 3)

A 
B
  B.1
    B.1.1
    B.1.2
  B.2
    B.2.1
    B.2.2

and if I expand A again, A.2 should still remain collapsed (children hidden) (example 4)

A 
  A.1 
    A.1.1 
    A.1.2 
  A.2 
B
  B.1
    B.1.1
    B.1.2
  B.2
    B.2.1
    B.2.2

I have tried code samples from http://jsfiddle.net/y4Mdy/1124/ -- but not handling three level hierarchies. $(this).nextUntil seems to work nicely, but when the next tr is a parent row, it gets collapsed until the next child row. Also, if I collapse the 2nd level and then collapse the parent, the 2nd levels are hidden but the 3rd level displays:

A 
    A.1.1 
    A.1.2       
B
  B.1
    B.1.1
    B.1.2
  B.2
    B.2.1
    B.2.2

I have also tried http://jsfiddle.net/icc97/XNkbE/ -- but sub levels do not go back to collapsed state when clicking on parent level (similar to my example 4 above)

Can anyone help?

Thanks in advance.

This solution should handle any number of levels.

My original jsfiddle is here . I will be updating it more often than this answer.

 $("tbody > tr:not([data-level='3'])").addClass("expandable sign folded"); $("tbody > tr:not([data-level='1'])").hide(); $("tbody > tr") .css("padding-left", function (index) { return 10 * parseInt($(this).data("level"), 10) + "px"; }); function range(lowEnd, highEnd) { // assert lowEnd >= 0 and highEnd < 100 var validation = (lowEnd <= highEnd) && (lowEnd >= 0) && (highEnd < 100); if (!(validation)) { console.assert(validation, 'Function "range" received unlikely values: ' + lowEnd + ' and ' + highEnd + "..."); } else { var arr = []; while (lowEnd <= highEnd) { arr.push(lowEnd++); } return arr; } } function name_range(fun, lowEnd, highEnd) { var arr = range(lowEnd, highEnd); jQuery.each(arr, function (index, value) { arr[index] = fun(value); }); return arr; } function create_selector(level) { return "[data-level='" + level + "']"; } $("tr.expandable").click(function () { var this_level = parseInt($(this).data("level"), 10); var this_level_selector = create_selector(this_level); var next_level_selector = create_selector(this_level + 1); var next_or_lower = name_range(create_selector, this_level + 1, 10); // TODO: find last level var this_or_higher = name_range(create_selector, 0, this_level); var node = $(this).prevUntil(this_or_higher.join(",")); // different behaviour according to state (expanded / folded): if ($(this).hasClass("expanded")) { $(this).removeClass("expanded").addClass('folded'); $(node).not("expanded").removeClass("expanded").addClass('folded'); $(node).filter(next_or_lower.join(",")).hide(); } else { $(this).addClass("expanded").removeClass('folded'); $(node).filter(next_level_selector).show(); } });
 table, tr, td, th { border: 1px solid black; border-collapse:collapse; border-color: gray; } tr:not([data-level='3']) { cursor:pointer; font-weight: bold; } tr.expanded .sign:after { content:"-"; } tr.folded .sign:after { content:"+"; } td:first-child { padding: inherit; }
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> <table border="0"> <thead> <tr> <th colspan="4">Header</th> </tr> </thead> <tbody> <tr data-level='3'> <td>data</td> <td>data</td> <td>data</td> <td>data</td> </tr> <tr data-level='3'> <td>data</td> <td>data</td> <td>data</td> <td>data</td> </tr> <tr data-level='2'> <td>sub stores<span class="sign"></span> </td> <td>total</td> <td>data</td> <td>data</td> </tr> <tr data-level='1'> <td>Trade<span class="sign"></span> </td> <td>total</td> <td>total</td> <td>data</td> </tr> <tr data-level='3'> <td>data</td> <td>data</td> <td>data</td> <td>data</td> </tr> <tr data-level='2'> <td>sub stores<span class="sign"></span> </td> <td>total</td> <td>data</td> <td>data</td> </tr> <tr data-level='3'> <td>data</td> <td>data</td> <td>data</td> <td>data</td> </tr> <tr data-level='2'> <td>sub stores<span class="sign"></span> </td> <td>total</td> <td>data</td> <td>data</td> </tr> <tr data-level='1'> <td>Stores<span class="sign"></span> </td> <td>total</td> <td>total</td> <td>data</td> </tr> </tbody> </table>

If we click on an element of the first level, we hide all visible elements on level 2 and 3. If the elements of the first level are already hidden we make the elements on level 2 visible again.

 toggleElement = function(context) { let current = context.parentElement if (current.className == 'level1') { toggleLevel1(current) } else if (current.className == 'level2') { toggleLevel2(current) } }; toggleLevel1 = function(current) { let nextSibling = current.nextElementSibling; while (nextSibling && nextSibling.className == 'level2') { nextSibling.hidden = nextSibling.hidden ? false : true nextSibling = nextSibling.nextElementSibling while (nextSibling && nextSibling.className == 'level3') { if (!nextSibling.hidden) { nextSibling.hidden = nextSibling.hidden ? false : true } nextSibling = nextSibling.nextElementSibling } } }; toggleLevel2 = function(current) { let nextSibling = current.nextElementSibling; while (nextSibling && nextSibling.className == 'level3') { nextSibling.hidden = nextSibling.hidden ? false : true nextSibling = nextSibling.nextElementSibling } };
 <html> <header> </header> <style> table, tr, td, th { border: 1px solid black; border-collapse:collapse; border-color: gray; } .level2 td:nth-child(1){ padding-left: 5px; } .level3 td:nth-child(1){ padding-left: 10px; } </style> <body> <table border="0"> <thead> <tr> <th colspan="4">Header</th> </tr> </thead> <tbody> <tr class="level1"> <td onclick="toggleElement(this)">Trade </td> <td>total1</td> <td>total1</td> <td>data1</td> </tr> <tr class="level2"> <td onclick="toggleElement(this)">sub stores </td> <td>total</td> <td>data</td> <td>data</td> </tr> <tr class="level3"> <td>data</td> <td>data</td> <td>data</td> <td>data</td> </tr> <tr class="level2"> <td onclick="toggleElement(this)">sub stores </td> <td>total2</td> <td>data2</td> <td>data2</td> </tr> <tr class="level3"> <td>data3</td> <td>data3</td> <td>data3</td> <td>data3</td> </tr> <tr class="level1"> <td onclick="toggleElement(this)">Trade </td> <td>total</td> <td>total</td> <td>data</td> </tr> <tr class="level2" > <td onclick="toggleElement(this)">sub stores3 </td> <td>total3</td> <td>data3</td> <td>data3</td> </tr> <tr class="level3"> <td>data4</td> <td>data4</td> <td>data4</td> <td>data4</td> </tr> <tr class="level2" > <td onclick="toggleElement(this)">sub stores5 </td> <td>total5</td> <td>data5</td> <td>data5</td> </tr> <tr class="level3"> <td>data6</td> <td>data6</td> <td>data6</td> <td>data6</td> </tr> </tbody> </table> </body> </html>

Since you are only dealing with nested table s, you can use this one:

CSS:

tbody.collapsed {
    display: none;
}

JS:

$('thead').click(function(){

        $(this).siblings('tbody').toggleClass('collapsed');

    });

I used thead and tbody to make use of semantics.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM