Search code examples
jqueryeachtablesorter

Jquery tablesorter - Include all child rows when selecting a parent row


This seems like an easy enough function but for some reason, I'm struggling to make it work. I have an HTML table with the jquery tablesorter plug in. Each row has a checkbox as the first column. By default, all child rows are hidden. When I select the checkbox on the parent row, I want it to also mark the checkboxes on the child rows under that parent as selected. Here is my HTML table:

<table>
  <thead>
    <tr>
      <th>Select</th>
      <th>Column2</th>
    </tr>
  </thead>
  <tbody>
     <tr class="tablesorter-hasChildRow">
       <td><input type="checkbox" class="rowSelector"></td>
       <td>Column 2 Data</td>
     <tr>
     <tr class="tablesorter-childRow">
       <td><input type="checkbox" class="rowSelector"></td>
       <td>Column 2 Data</td>
     <tr>
     <tr class="tablesorter-childRow">
       <td><input type="checkbox" class="rowSelector"></td>
       <td>Column 2 Data</td>
     <tr>
     <tr class="tablesorter-hasChildRow">
       <td><input type="checkbox" class="rowSelector"></td>
       <td>Column 2 Data</td>
     <tr>
     <tr class="tablesorter-childRow">
       <td><input type="checkbox" class="rowSelector"></td>
       <td>Column 2 Data</td>
     <tr>
     <tr class="tablesorter-childRow">
       <td><input type="checkbox" class="rowSelector"></td>
       <td>Column 2 Data</td>
     <tr>
  </tbody>
 </table>

And here is my javascript. I get to the point where the alert works, the part I'm having a hard time with is now selecting all child rows under this parent (the number of child rows will vary from parent to parent) & then select the checkbox. In other areas, I used prop('checked') but I need it to toggle so that if the parent is UN-checked, the children will uncheck as well.

$(.rowSelector').change(function() {
    if ($(this).closest('tr').hasClass('tablesorter-hasChildRow')) {
        alert('Yay!');  //this part works
        $(this).nextAll('tr').each(function () {
            if ($(this).has('.tablesorter-childRow')) {
              $(this).find(input.rowSelector).toggleClass('selected');
             }
        });
   };
});

Can anyone tell me where I'm going wrong here?


Solution

  • There are some issues with the HTML... make sure to use </tr> when closing a row. This HTML will create two rows, the first being empty.

    <tr>
    <tr class="tablesorter-hasChildRow">
    

    Anyway, I created this demo which does all the necessary checkbox determination that @rtytgat mentioned:

    • Setting a parent will make all children have the same check state
    • Child rows with a combo of checked & unchecked will make the parent indeterminate.
    • If all child rows have the same state, the parent is set to that state.
    $(function() {
    
      var parentClass = '.tablesorter-hasChildRow',
        rowSelector = '.rowSelector';
    
      // return parent + all children
      function getGroupRows($row) {
        var isParent = $row.hasClass(parentClass.slice(1)),
          $rows = $row.nextUntil(parentClass).add($row),
          $prev = $row.prevUntil(parentClass).add($row);
        return isParent ? $rows : $rows.add($prev).add($prev.prev());
      }
    
      $('table')
        .tablesorter({
          theme: 'blue'
        })
        .on('change', rowSelector, function() {
          var $rows, checked, len,
            isChecked = this.checked,
            $row = $(this).closest('tr'),
            $group = getGroupRows($row);
          if ($row.hasClass(parentClass.slice(1))) {
            // parent checked, now (un)check all children
            $group.find('.rowSelector').prop('checked', isChecked);
          } else {
            // child row (un)checked, now figure out what to do with the parent
            $rows = $group.filter('.tablesorter-childRow');
            checked = $rows.find(rowSelector + ':checked').length;
            len = isChecked ? checked : $rows.length - checked;
            if (len === $rows.length) {
              // all child rows are the same, set the parent to match
              $group.filter(parentClass).find(rowSelector).prop({
                indeterminate: false,
                checked: isChecked
              });
            } else {
              // combo of checked & unchecked, make parent indeterminate
              $group.filter(parentClass).find(rowSelector).prop({
                indeterminate: true,
                checked: false
              });
            }
          }
        });
    });