Recursive HTML table tree using only JavaScript

后端 未结 1 1764
别跟我提以往
别跟我提以往 2021-01-21 21:23

I developed clicking on the parent node to display its child row. I just need to enable to click on the child data should open its sub child rows as recursive one or table tree.

相关标签:
1条回答
  • 2021-01-21 21:44

    Updated answer

    I've changed almost everything and simplified the code:

    • The toggle buttons are added automatically,
    • + changes to - when the parent is opened,
    • The table, the classes for opened and visible elements, and the buttons are passed as parameters,
    • It could be used on multiple table,

    I've created a repository with that code on GitHub:
    https://github.com/TakitIsy/table-to-tree

    /* ---- < MAIN FUNCTION > ---- */
    function tableToTree(table_Selector, tr_OpenedClass, tr_VisibleClass, tr_ToggleButton) {
    
      // Table elements variables
      var table = document.querySelector(table_Selector);
      var trs = document.querySelectorAll(table_Selector + " tr");
    
      // Add the buttons above the table
      var buttons = document.createElement('div');
      buttons.innerHTML = '<button>[‒] All</button><button>[+] All</button>';
      table.insertBefore(buttons, table.childNodes[0]);
      buttons = buttons.querySelectorAll('button');
      // Add the actions of these buttons
      buttons[0].addEventListener("click", function() {
        trs.forEach(function(elm) {
          elm.classList.remove(tr_OpenedClass);
          elm.classList.remove(tr_VisibleClass);
        });
      });
      buttons[1].addEventListener("click", function() {
        trs.forEach(function(elm) {
          if (elm.innerHTML.includes(tr_ToggleButton))
            elm.classList.add(tr_OpenedClass);
          elm.classList.add(tr_VisibleClass);
        });
      });
    
      // Next tr function
      function nextTr(row) {
        while ((row = row.nextSibling) && row.nodeType != 1);
        return row;
      }
    
      // On creation, automatically add toggle buttons if the tr has childs elements
      trs.forEach(function(tr, index) {
        if (index < trs.length - 1) {
          if (+tr.getAttribute("level") < +trs[index + 1].getAttribute("level")) {
            var elm1 = tr.firstElementChild;
            elm1.innerHTML = tr_ToggleButton + elm1.innerHTML;
          }
        }
      });
    
      // Use the buttons added by the function above
      table.addEventListener("click", function(e) {
        
        // Event management
        if (!e) return;
        if (e.target.outerHTML !== tr_ToggleButton) return;
        e.preventDefault();
        
        // Get the parent tr and its level
        var row = e.target.closest("tr");
        row.classList.toggle(tr_OpenedClass);
        var lvl = +(row.getAttribute("level"));
    
        // Loop to make childs visible/hidden
        while ((row = nextTr(row)) && ((+(row.getAttribute("level")) == (lvl + 1)) || row.className.includes(tr_VisibleClass))) {
          row.classList.remove(tr_OpenedClass);
          row.classList.toggle(tr_VisibleClass);
        }
      });
    
    }
    /* ---- </ MAIN FUNCTION > ---- */
    
    // Call the above main function to make the table tree-like
    tableToTree('#myTable', 'opened', 'visible', '<span class="toggle"></span>');
    tbody tr {
      display: none;
    }
    
    tr[level="0"],
    tr.visible {
      display: table-row;
    }
    
    td {
      background: #ccc;
      padding: 4px 8px 4px 32px;
      text-align: left;
    }
    
    tr[level="1"] td {
      background: #ffffd;
      padding-left: 40px;
    }
    
    tr[level="2"] td {
      background: #eee;
      padding-left: 48px;
    }
    
    tr .toggle {
      position: absolute;
      left: 16px;
      cursor: pointer;
    }
    
    .toggle::after {
      content: "[+]";
    }
    
    .opened .toggle::after {
      content: "[‒]";
    }
    <table id="myTable">
      <tbody>
        <tr level="0">
          <td>Parent 1</td>
        </tr>
        <tr level="1">
          <td>Match 1</td>
        </tr>
        <tr level="1">
          <td>Match 2</td>
        </tr>
        <tr level="0">
          <td>Parent 2</td>
        </tr>
        <tr level="1">
          <td>Mismatch 1</td>
        </tr>
        <tr level="1">
          <td>Mismatch 2</td>
        </tr>
        <tr level="2">
          <td>Mismatch 2.1</td>
        </tr>
      </tbody>
    </table>
    <br>


    ⋅ ⋅ ⋅

    Old answer

    I played a little with your code…
    Trying to use as many as possible of existing functions/methods to make the code cleaner and easier to read and understand.

    … and ended-up with that snippet:
    (See comments in my code for more details)

    document.getElementById("products").addEventListener("click", function(e) {
      // I think using the not equal is nicer here, just my opinion… Less indenting.
      if (!e) return;                       // Exit if null
      if (e.target.tagName !== "A") return; // Exit if not A element
    
      e.preventDefault();
      var row = e.target.closest("tr"); // Better than parent parent!
      var cls = row.classList[0];       // Get the first class name (the only one in our html)
      var lvl = +(cls.slice(-1)) + 1;   // Unary operator +1 on the last character
      cls = cls.slice(0, -1) + lvl;     // Replace the last char with lvl to get the class to be toggled
    
      // Check if the row is of the class to be displayed OR if the row is already opened
      // (so that clicking close on parent also closes sub-childs)
      while ((row = nextTr(row)) && (row.className.includes(cls) || row.className.includes("open")))
        row.classList.toggle("open"); // Better than the function you created, it already exists!
    });
    
    function nextTr(row) {
      while ((row = row.nextSibling) && row.nodeType != 1);
      return row;
    }
    
    // Select all the tr childs elements (all levels except 0
    var allChilds = document.querySelectorAll("tr[class^=level]:not(.level0)");
    // Added the below for buttons after comments
    document.getElementById("openAll").addEventListener("click", function() {
      allChilds.forEach(function(elm){
        elm.classList.add("open");
      });
    });
    document.getElementById("closeAll").addEventListener("click", function() {
      allChilds.forEach(function(elm){
        elm.classList.remove("open");
      });
    });
    tbody tr {
      display: none;
    }
    
    
    /* Simplified */
    
    tr.level0,
    tr.open {
      display: table-row;
    }
    
    
    /* Added colors for better visibility */
    
    tr.level0 {
      background: #ccc;
    }
    
    tr.level1 {
      background: #ffffd;
    }
    
    tr.level2 {
      background: #eee;
    }
    
    
    /* Added some more styling after comment */
    
    tr td {
      padding: 0.2em 0.4em;
    }
    
    tr td:first-of-type {
      position: relative;
      padding: 0.2em 1em;
    }
    
    tr td a {
      color: inherit;
      /* No special color */
      text-decoration: inherit;
      /* No underline */
      position: absolute;
      left: 0.25em;
    }
    
    tr.level1 td:first-of-type {
      padding-left: 1.5em;
    }
    
    tr.level2 td:first-of-type {
      padding-left: 2em;
    }
    <button id="openAll">+ All</button>
    <button id="closeAll">- All</button>
    <table class="table" id="products">
      <thead>
        <tr>
          <th>Product</th>
          <th>Price</th>
          <th>Destination</th>
          <th>Updated on</th>
        </tr>
      </thead>
      <tbody>
        <tr class="level0">
          <td><a href="#">+</a>Oranges</td>
          <td>100</td>
          <td>On Store</td>
          <td>22/10</td>
        </tr>
        <tr class="level1">
          <td>121</td>
          <td>120</td>
          <td>City 1</td>
          <td>22/10</td>
        </tr>
        <tr class="level1">
          <td><a href="#">+</a>212</td>
          <td>140</td>
          <td>City 2</td>
          <td>22/10</td>
        </tr>
        <tr class="level2">
          <td>212</td>
          <td>140</td>
          <td>City 2</td>
          <td>22/10</td>
        </tr>
        <tr class="level2">
          <td>212</td>
          <td>140</td>
          <td>City 2</td>
          <td>22/10</td>
        </tr>
        <tr class="level0">
          <td><a href="#">+</a>Apples</td>
          <td>100</td>
          <td>On Store</td>
          <td>22/10</td>
        </tr>
        <tr class="level1">
          <td>120</td>
          <td>120</td>
          <td>City 1</td>
          <td>22/10</td>
        </tr>
        <tr class="level1">
          <td><a href="#">+</a>120</td>
          <td>140</td>
          <td>City 2</td>
          <td>22/10</td>
          <tr class="level2">
            <td>120</td>
            <td>140</td>
            <td>City 2</td>
            <td>22/10</td>
          </tr>
          <tr class="level2">
            <td>120</td>
            <td>140</td>
            <td>City 2</td>
            <td>22/10</td>
          </tr>
      </tbody>
    </table>

    I hope it helps!

    0 讨论(0)
提交回复
热议问题