50

I have some data to display that is both tabular and hierarchical. I'd like to let the user be able to expand and collapse the nodes.

Sort of like this, except functional:

http://www.maxdesign.com.au/articles/tree-table/

What would be the best way to approach this? I'm not adverse to using an off-the-shelf plugin.

6 Answers 6

45

SlickGrid has this functionality, see the tree demo.

If you want to build your own, here is an example (jsFiddle demo): Build your table with a data-depth attribute to indicate the depth of the item in the tree (the levelX CSS classes are just for styling indentation): 

<table id="mytable">
    <tr data-depth="0" class="collapse level0">
        <td><span class="toggle collapse"></span>Item 1</td>
        <td>123</td>
    </tr>
    <tr data-depth="1" class="collapse level1">
        <td><span class="toggle"></span>Item 2</td>
        <td>123</td>
    </tr>
</table>

Then when a toggle link is clicked, use Javascript to hide all <tr> elements until a <tr> of equal or less depth is found (excluding those already collapsed):

$(function() {
    $('#mytable').on('click', '.toggle', function () {
        //Gets all <tr>'s  of greater depth below element in the table
        var findChildren = function (tr) {
            var depth = tr.data('depth');
            return tr.nextUntil($('tr').filter(function () {
                return $(this).data('depth') <= depth;
            }));
        };

        var el = $(this);
        var tr = el.closest('tr'); //Get <tr> parent of toggle button
        var children = findChildren(tr);

        //Remove already collapsed nodes from children so that we don't
        //make them visible. 
        //(Confused? Remove this code and close Item 2, close Item 1 
        //then open Item 1 again, then you will understand)
        var subnodes = children.filter('.expand');
        subnodes.each(function () {
            var subnode = $(this);
            var subnodeChildren = findChildren(subnode);
            children = children.not(subnodeChildren);
        });

        //Change icon and hide/show children
        if (tr.hasClass('collapse')) {
            tr.removeClass('collapse').addClass('expand');
            children.hide();
        } else {
            tr.removeClass('expand').addClass('collapse');
            children.show();
        }
        return children;
    });
});
Sign up to request clarification or add additional context in comments.

3 Comments

Very nice:-) Just a small correction: Comment should be: // Gets all <tr>'s of greater depth.. NOT // Gets all <tr>'s of greater or equal depth.. It is only the tr's with a grater depth that we want to hide. Equal depth we want to show:) The logic is still right.
this comment might get flagged, but i don't care, i want to thank you for that simple implementation of collapsible tree grid. you deserve a thousand upvote!
Looks good! Can you tell how it can be initially started as everything collapsed?
40

In modern browsers, you need only very little to code to create a collapsible tree :

var tree = document.querySelectorAll('ul.tree a:not(:last-child)');
for(var i = 0; i < tree.length; i++){
    tree[i].addEventListener('click', function(e) {
        var parent = e.target.parentElement;
        var classList = parent.classList;
        if(classList.contains("open")) {
            classList.remove('open');
            var opensubs = parent.querySelectorAll(':scope .open');
            for(var i = 0; i < opensubs.length; i++){
                opensubs[i].classList.remove('open');
            }
        } else {
            classList.add('open');
        }
        e.preventDefault();
    });
}
body {
    font-family: Arial;
}

ul.tree li {
    list-style-type: none;
    position: relative;
}

ul.tree li ul {
    display: none;
}

ul.tree li.open > ul {
    display: block;
}

ul.tree li a {
    color: black;
    text-decoration: none;
}

ul.tree li a:before {
    height: 1em;
    padding:0 .1em;
    font-size: .8em;
    display: block;
    position: absolute;
    left: -1.3em;
    top: .2em;
}

ul.tree li > a:not(:last-child):before {
    content: '+';
}

ul.tree li.open > a:not(:last-child):before {
    content: '-';
}
<ul class="tree">
  <li><a href="#">Part 1</a>
    <ul>
      <li><a href="#">Item A</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item B</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item C</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item D</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item E</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
    </ul>
  </li>

  <li><a href="#">Part 2</a>
    <ul>
      <li><a href="#">Item A</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item B</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item C</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item D</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item E</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
    </ul>
  </li>

  <li><a href="#">Part 3</a>
    <ul>
      <li><a href="#">Item A</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item B</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item C</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item D</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
      <li><a href="#">Item E</a>
        <ul>
          <li><a href="#">Sub-item 1</a></li>
          <li><a href="#">Sub-item 2</a></li>
          <li><a href="#">Sub-item 3</a></li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

(see also this Fiddle)

12 Comments

Thank you for your wonderful answer. I'm trying to implement your solution but it doesn't seem to work in my setup. I've posted a question about this. Please let me know what am I doing wrong? stackoverflow.com/questions/43016555/…
@AnandTyagi : Can you post your code on JSFiddle, or somewhere else online? I'll need to be able to debug your code to figure out what's wrong with it!
I know this is old, but I found this to be the simplest and best answer. Works perfect in my project
This code does not take care of the tabular part of the data.
@JohnSlegers Yes. The OP specifically asked about displaying data that is "both tabular and hierarchical". Your example shows hierarchical data, but no tabular data, which should be displayed in columns to the right of the tree (see the OP's link for an example).
|
18

jquery is your friend here.

http://docs.jquery.com/UI/Tree

If you want to make your own, here is some high level guidance:

Display all of your data as <ul /> elements with the inner data as nested <ul />, and then use the jquery:

$('.ulClass').click(function(){ $(this).children().toggle(); });

I believe that is correct. Something like that.

EDIT:

Here is a complete example.

 $(".Collapsable").click(function () {

    $(this).parent().children().toggle();
    $(this).toggle();

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul>
    <li><span class="Collapsable">item 1</span><ul>
        <li><span class="Collapsable">item 1</span></li>
        <li><span class="Collapsable">item 2</span><ul>
            <li><span class="Collapsable">item 1</span></li>
            <li><span class="Collapsable">item 2</span></li>
            <li><span class="Collapsable">item 3</span></li>
            <li><span class="Collapsable">item 4</span></li>
        </ul>
        </li>
        <li><span class="Collapsable">item 3</span></li>
        <li><span class="Collapsable">item 4</span><ul>
            <li><span class="Collapsable">item 1</span></li>
            <li><span class="Collapsable">item 2</span></li>
            <li><span class="Collapsable">item 3</span></li>
            <li><span class="Collapsable">item 4</span></li>
        </ul>
        </li>
    </ul>
    </li>
    <li><span class="Collapsable">item 2</span><ul>
        <li><span class="Collapsable">item 1</span></li>
        <li><span class="Collapsable">item 2</span></li>
        <li><span class="Collapsable">item 3</span></li>
        <li><span class="Collapsable">item 4</span></li>
    </ul>
    </li>
    <li><span class="Collapsable">item 3</span><ul>
        <li><span class="Collapsable">item 1</span></li>
        <li><span class="Collapsable">item 2</span></li>
        <li><span class="Collapsable">item 3</span></li>
        <li><span class="Collapsable">item 4</span></li>
    </ul>
    </li>
    <li><span class="Collapsable">item 4</span></li>
</ul>

7 Comments

This works pretty well, but for me it starts out with all paths expanded - what needs to be modified so that it starts with everything collapsed to the first level? *Edit: I erased my message to edit it - thanks to @joe_coolish for the quick response. .each() is what I needed
No prob :) jQuery is awesome! You can do so much so quickly! p.s., I updated the jsfiddle to be more reusable. Here is the new link: jsfiddle.net/5Q8rJ/1
This is exactly what I want, I'm just wondering how would I set the background to be alternating and full width, i.e. item 4 in item 2 in item 1, has the same width background as item1? So as to make it appear like a "tree-table".
@user986697 - I'm not sure what you mean? Are you talking about CSS?
This code does not take care of the tabular part of the data
|
6

I'll throw jsTree into the ring, too. I've found it fairly adaptable to your particular situation. It's packed as a jQuery plugin.

It can run from a variety of data sources, but my favorite is a simple nested list, as described by @joe_coolish or here:

<ul>
  <li>
    Item 1
    <ul>
      <li>Item 1.1</li>
      ...
    </ul>
  </li>
  ...
</ul>

This structure fails gracefully into a static tree when JS is not available in the client, and is easy enough to read and understand from a coding perspective.

1 Comment

This code does not take care of the tabular part of the data
6

HTML 5 allows summary tag, details element. That can be used to view or hide (collapse/expand) a section. Link

2 Comments

Man got my hopes up there for a second.
This was good enough for me to create a quick and dirty tree view. To create the indentation for the lower levels, I used unordered lists with hidden bullets (<ul style="list-style-type: none">) within the details block. Nesting several levels deep works as expected.
4

You can try jQuery treegrid (http://maxazan.github.io/jquery-treegrid/) or jQuery treetable (http://ludo.cubicphuse.nl/jquery-treetable/)

Both are using HTML <table> tag format and styled the as tree.

The jQuery treetable is using data-tt-id and data-tt-parent-id for determining the parent and child of the tree. Usage example:

<table id="tree">
  <tr data-tt-id="1">
    <td>Parent</td>
  </tr>
  <tr data-tt-id="2" data-tt-parent-id="1">
    <td>Child</td>
  </tr>
</table>
$("#tree").treetable({ expandable: true });

Meanwhile, jQuery treegrid is using only class for styling the tree. Usage example:

<table class="tree">
    <tr class="treegrid-1">
        <td>Root node</td><td>Additional info</td>
    </tr>
    <tr class="treegrid-2 treegrid-parent-1">
        <td>Node 1-1</td><td>Additional info</td>
    </tr>
    <tr class="treegrid-3 treegrid-parent-1">
        <td>Node 1-2</td><td>Additional info</td>
    </tr>
    <tr class="treegrid-4 treegrid-parent-3">
        <td>Node 1-2-1</td><td>Additional info</td>
    </tr>
</table>
<script type="text/javascript">
  $('.tree').treegrid();
</script>

1 Comment

This has performance issues with huge data. just for 1500 nodes it takes approx 1 minute.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.