Collapse tree table plain JS

by Huy Vo

HTML

<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>
  <tr data-depth="2" class="collapse level2">
    <td>Item 3a</td>
    <td>123</td>
  </tr>
  <tr data-depth="2" class="collapse level2">
    <td>Item 3b</td>
    <td>123</td>
  </tr>
  <tr data-depth="2" class="collapse level2">
    <td>Item 3c</td>
    <td>123</td>
  </tr>
  <tr data-depth="1" class="collapse level1">
    <td>Item 4</td>
    <td>123</td>
  </tr>
  <tr data-depth="0" class="collapse collapsable level0">
    <td><span class="toggle collapse"></span>Item 5</td>
    <td>123</td>
  </tr>
  <tr data-depth="1" class="collapse collapsable level1">
    <td>Item 6</td>
    <td>123</td>
  </tr>
</table>

CSS

table td {
  border: 1px solid #eee;
}

.level1 td:first-child {
  padding-left: 15px;
}

.level2 td:first-child {
  padding-left: 30px;
}

.collapse .toggle {
  background: url("http://mleibman.github.com/SlickGrid/images/collapse.gif");
}

.expand .toggle {
  background: url("http://mleibman.github.com/SlickGrid/images/expand.gif");
}

.toggle {
  height: 9px;
  width: 9px;
  display: inline-block;
}

JavaScript

[].forEach.call(document.querySelectorAll('#mytable .toggle'), function(el) {
  el.addEventListener('click', function() {
    var el = this;
    var tr = el.closest('tr');
    var children = findChildren(tr);
    var subnodes = children.filter(function(element) {
      return element.matches('.expand');
    });
    subnodes.forEach(function(subnode) {
      var subnodeChildren = findChildren(subnode);
      children = children.filter(function(element) {
          return !subnodeChildren.includes(element);
      });
            console.log(children);
      //children = children.not(subnodeChildren);
    });
    if (tr.classList.contains('collapse')) {
      tr.classList.remove('collapse');
      tr.classList.add('expand');
      children.forEach(function(child) {
        child.style.display = 'none';
      });
    } else {
      tr.classList.remove('expand');
      tr.classList.add('collapse');
      children.forEach(function(child) {
        child.style.display = '';
      });
    }
  })
})

var findChildren = function(tr) {
  var depth = tr.dataset.depth;
  var elements = [...document.querySelectorAll('#mytable tr')].filter(function(element) {
    return element.dataset.depth <= depth;
  });
  var next = nextUntil(tr, elements);
  return next;
};

var nextUntil = function(elem, elements, filter) {
  var siblings = [];
  elem = elem.nextElementSibling;
  while (elem) {
    if (elements.includes(elem)) break;
    if (filter && !elem.matches(filter)) {
      elem = elem.nextElementSibling;
      continue;
    }
    siblings.push(elem);
    elem = elem.nextElementSibling;
  }
  return siblings;
};