Search code examples
jquerycsscss-tables

Toggle table - increase rowspan to accommodate extra toggled row


I have a table that can be toggled fine with a button or by clicking on the row. The problem is when the 1st column is spanned over 2 or more rows everything goes out of sync. Also would like the bottom border to move under the toggled area and the hovered area background colour to line up with additional rows (currently only works with 1st row because of the rowspan). Hopefully this makes sense and someone can help with this.

$(document).ready(function() {
  $("#hide").click(function() {
    $(".toggle").hide();
  });
  $("#show").click(function() {
    $(".toggle").show();
  });
  $("#data tr td").click(function() {
    $(this).parents(".pointer").next().toggle();
  });
});
table {
  border-collapse: collapse;
  margin-top: 20px;
}

table,
th {
  border: solid 1px;
  padding: 10px;
}

tr:hover td {
    background: #e5e5e5;
}

.toggle {
  display: none;
}

.toggle.active {
  display: table-row;
}

.pointer {
  cursor: pointer;
  border: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<button id="hide">Hide All</button>
<button id="show">Show All</button>
<table>
  <tr>
    <th>header</th>
    <th>header</th>
    <th>header</th>
    <th>header</th>
    <th>header</th>
  </tr>
  <tbody id="data">
  <tr class="pointer">
      <td style="border-bottom:1px solid;" >DATA1</td>
      <td style="border-bottom:1px solid;">data1</td>
      <td style="border-bottom:1px solid;">data1</td>
      <td style="border-bottom:1px solid;">data1</td>
      <td style="border-bottom:1px solid;">data1</td>
    </tr>
    <tr class="toggle">
      <td></td>
      <td colspan="4">toggled area 1a</td>
    </tr>
    <tr class="pointer">
      <td  style="border-bottom:1px solid;" rowspan="2">DATA2</td>
      <td>data2</td>
      <td>data2</td>
      <td>data2</td>
      <td>data2</td>
    </tr>
    <tr class="toggle">
      <td></td>
      <td colspan="4">toggled area 2a</td>
    </tr>
    <tr class="pointer">
      <td style="border-bottom:1px solid;">data2</td>
      <td style="border-bottom:1px solid;">data2</td>
      <td style="border-bottom:1px solid;">data2</td>
      <td style="border-bottom:1px solid;">data2</td>
    </tr>
    <tr class="toggle">
      <td></td>
      <td colspan="4">toggled area 2b</td>
    </tr>
    <tr class="pointer">
      <td rowspan="3">DATA3</td>
      <td>data3</td>
      <td>data3</td>
      <td>data3</td>
      <td>data3</td>
    </tr>
    <tr class="toggle">
      <td></td>
      <td colspan="4">toggled area 3a</td>
    </tr>
    <tr class="pointer">
      <td>data3</td>
      <td>data3</td>
      <td>data3</td>
      <td>data3</td>
    </tr>
    <tr class="toggle">
     <td></td>
      <td colspan="4">toggled area 3b</td>
    </tr>
    <tr class="pointer">
      <td>data3</td>
      <td>data3</td>
      <td>data3</td>
      <td>data3</td>
    </tr>
    <tr class="toggle">
      <td></td>
      <td colspan="4">toggled area 3c</td>
    </tr>
  </tbody>
</table>


Solution

    • First wrap each data area in a <tbody>.
    • All tr.toggle should have only one <td> and it should have colspan='4'
    • Only the first <td> of each <tbody> has the class .pointer and a rowspan equal to the number of initially visible rows of said <tbody>. ex. first <tbody> has one row initially so td.pointer in that <tbody> has a rowspan='1'.
    • When a <td> other than a <td> nested within a tr.toggle is clicked and is now visible, the corresponding .pointer rowspan increases by 1.
    • .pointer rowspan decreases by 1 when a tr.toggle is not visible.
    • The last <tr> of data area 1 and 2 has an unclickable <td colspan='5'> which contains a <hr>.

    $("#all").on('click', function(e) {
      $(this).toggleClass('show hide');
      toggleRow(e);
    });
    
    $('.pointer').on('click', toggleRow);
    $("td").not('.toggle > td').on('click', toggleRow);
    
    function toggleRow(e) {
      if (e.target.matches('td')) {
        var toggle = $(this).parent().next('.toggle');
        var data = $(this).closest('tbody');
        var pointer = data.find('.pointer');
        var rowspan = Number(pointer.attr('rowspan'));
        toggle.toggle();
        if (toggle.is(':visible')) {
          pointer.prop('rowspan', `${rowspan+=1}`);
          if (e.target.matches('.borderA')) {
            data.find('.borderA').css('border-width', '0');
          }
        } else {
          pointer.prop('rowspan', `${rowspan-=1}`);
          if (e.target.matches('.borderA')) {
            data.find('.borderA').css('border-width', '1px');
          }
        }
      } else if (e.target.matches('#all')) {
        $('tbody').each(function() {
          var pointer = $(this).find('.pointer');
          var rows = $(this).children().length;
          var rowspan = Number(pointer.attr('rowspan'));
          if ($('#all').hasClass('hide')) {
            $('.toggle').show();
            pointer.attr('rowspan', rows);
            $(this).find('.borderA').css('border-width', '0');
          } else {
            $('.toggle').hide();
            pointer.attr('rowspan', rowspan);
            $(this).find('.borderA').css('border-width', '1px');
          }
        });
      }
      return false;
    };
    table {
      margin-top: 20px;
      border-spacing: 0;
    }
    
    table,
    th {
      border: solid 1px #000;
    }
    
    tr:hover td,
    tbody:hover .pointer {
      background: #e5e5e5;
    }
    
    .toggle {
      display: none;
      text-align: center
    }
    
    .pointer {
      border: 0;
      border-bottom: 1px solid #000;
    }
    
    .border,
    .borderA {
      border-bottom: 1px solid #000;
    }
    
    #all {
      width: 10ch;
    }
    
    #all.hide::before {
      content: 'Hide ';
    }
    
    #all.show::before {
      content: 'Show ';
    }
    <button id="all" class='show'>All</button>
    
    <table>
      <thead>
        <tr>
          <th>header</th>
          <th>header</th>
          <th>header</th>
          <th>header</th>
          <th>header</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th class="pointer show" rowspan='1'>DATA1</th>
          <td class='borderA'>data1</td>
          <td class='borderA'>data1</td>
          <td class='borderA'>data1</td>
          <td class='borderA'>data1</td>
        </tr>
        <tr class="toggle">
          <td class='border' colspan="4">1A</td>
        </tr>
      </tbody>
      <tbody>
        <tr>
          <th class='pointer show' rowspan="2">DATA2</th>
          <td>data2</td>
          <td>data2</td>
          <td>data2</td>
          <td>data2</td>
        </tr>
        <tr class="toggle">
          <td colspan="4">2A</td>
        </tr>
        <tr>
          <td class='borderA'>data2</td>
          <td class='borderA'>data2</td>
          <td class='borderA'>data2</td>
          <td class='borderA'>data2</td>
        </tr>
        <tr class="toggle">
          <td class='border' colspan="4">2B</td>
        </tr>
      </tbody>
      <tbody>
        <tr>
          <th class="pointer show" rowspan="3">DATA3</th>
          <td>data3</td>
          <td>data3</td>
          <td>data3</td>
          <td>data3</td>
        </tr>
        <tr class="toggle">
          <td colspan="4">3A</td>
        </tr>
        <tr>
          <td>data3</td>
          <td>data3</td>
          <td>data3</td>
          <td>data3</td>
        </tr>
        <tr class="toggle">
          <td colspan="4">3B</td>
        </tr>
        <tr>
          <td class='borderA'>data3</td>
          <td class='borderA'>data3</td>
          <td class='borderA'>data3</td>
          <td class='borderA'>data3</td>
        </tr>
        <tr class="toggle">
          <td class='border' colspan="4">3C</td>
        </tr>
      </tbody>
    </table>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>