javascriptdomevent-handlingselectors-apievent-delegation

Why does my checkbox change-handling select or deselect all rows in every table instead of just the current one?


I have multiple tables on a page. Each table, has a "check all" checkbox in the header. In the body, there is another checkbox for each row.

When the user checks each boy row, then a active class is applied and highlights the marked row, and the counter increases/decreases.

I have a problem with the check all function.

When the user selects the check all checkbox in the header, then it should select all the rows in just that one table. I can only get it to check all the rows across all the tables. Also the counter counts all the rows across all the tables, rather than just that one table.

Where am I going wrong?

Here is my code:

// https://gomakethings.com/a-vanilla-js-foreach-helper-method/
var forEach = function forEach(arr, callback) {
  Array.prototype.forEach.call(arr, callback);
};

var tableInputs = document.querySelectorAll('.table tbody td .form-check-input');
var tableSelectAll = document.querySelectorAll('.table thead th .form-check-input');
var count = document.querySelector('.output span')

forEach(tableInputs, function(element) {
  element.addEventListener('change', function() {
    // active class to make row blue
    if (element.checked) {
      element.parentNode.parentNode.classList.add('active');
    } else {
      element.parentNode.parentNode.classList.remove('active');
    }

    // set count to -
    var numberSelected = 0;

    // count number of checked
    for (var i = 0; i < tableInputs.length; i++) {
      if (tableInputs[i].checked == true) {
        numberSelected++;
      }
    }

    // display the count
    count.innerHTML = numberSelected;
  });
});

forEach(tableSelectAll, function(element) {
  element.addEventListener('change', function() {

    if (element.checked == true) {
      forEach(tableInputs, function(input) {
        input.parentNode.parentNode.classList.add('active');
        input.checked = true;

        // set count to -
        var numberSelected = 0;

        // count number of checked
        for (var i = 0; i < tableInputs.length; i++) {
          if (tableInputs[i].checked == true) {
            numberSelected++;
          }
        }

        // display the count
        count.innerHTML = numberSelected;
      });
    } else {
      forEach(tableInputs, function(input) {
        input.parentNode.parentNode.classList.remove('active');
        input.checked = false;
        count.innerHTML = 0;
      });
    }
  });
});
.form-check-input {
  border: solid 1px #000;
  position: relative;
}

tr.active {
  background-color: lightblue;
}

body { margin: 0; zoom: .88; }
p { margin: 0; }
<div class="container">
  <div class="row">
    <div class="col-12">
      <p>Table 1</p>

      <table class="table table-sm table-borderless">
        <thead>
          <tr>
            <th><input class="form-check-input" type="checkbox" value=""></th>
            <th>Request date</th>
            <th>Name</th>
            <th>Organisation/Employer</th>
            <th>Selected Course(s)</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><input class="form-check-input" type="checkbox" value=""></td>
            <td>10/10/2014</td>
            <td><a href="#">Clark Kent</a></td>
            <td><span>Daily Planet</span></td>
            <td><span>Flight</span></td>
          </tr>
          <tr>
            <td><input class="form-check-input" type="checkbox" value=""></td>
            <td>10/10/2014</td>
            <td><a href="#">Hal Jordan</a></td>
            <td><span>Green Lantern Corps</span></td>
            <td>Lighting</td>
          </tr>
          <tr>
            <td><input class="form-check-input" type="checkbox" value=""></td>
            <td>10/10/2014</td>
            <td><a href="#">Arthur Curry</a></td>
            <td><span>Atlantis Water</span></td>
            <td>Aquatics</td>
          </tr>
        </tbody>
      </table>

      <p>Table 2</p>
      <table class="table table-sm table-borderless ">
        <thead>
          <tr>
            <th><input class="form-check-input" type="checkbox" value=""></th>
            <th>Request date</th>
            <th>Name</th>
            <th>Organisation/Employer</th>
            <th>Selected Course(s)</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><input class="form-check-input" type="checkbox" value=""></td>
            <td>10/10/2014</td>
            <td><a href="#">Barry Allen</a></td>
            <td><span>Star Labs</span></td>
            <td><span>Speed</span></td>
          </tr>
          <tr>
            <td><input class="form-check-input" type="checkbox" value=""></td>
            <td>10/10/2014</td>
            <td><a href="#">Bruce Wayne</a></td>
            <td><span>Wayne Enterprises</span></td>
            <td>Combat</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

<p class="output">Total selected: <span>0</span></p>


Solution

  • Regardless of the approach one always should break down the problem into specific tasks which for the OP's requirements are ...

    The techniques/tools are Event Delegation and the Selectors API

    At any checkbox state change the handler inspects the event target whether it belongs to the current table's header or body.

    Based on this check one either, for the first case, needs to check/uncheck all of a current table's body-related checkboxes or, according to the second case, one needs to update the state of the sole header-related checkbox.

    Updating the checkbox counter is achieved by the correct selector and the queried node list's length value.

    function updateCheckboxCounter() {
      document
        .querySelector('.output > span')
        .textContent = document
          .querySelectorAll('table.table tbody [type="checkbox"]:checked')
          .length;
    }
    
    function updateTableRowActiveState(checkboxNode) {
      checkboxNode
        .closest('tr')
        .classList
        .toggle('active', checkboxNode.checked);
    }
    
    function updateCheckboxDependedStates({ target }) {
      const tableNode = target.closest('table.table');
    
      if (target.matches('thead [type="checkbox"]')) {
    
        tableNode
          .querySelectorAll('tbody [type="checkbox"]')
          .forEach(elmNode => {
    
            elmNode.checked = target.checked;
    
            updateTableRowActiveState(elmNode);
          });
    
      } else if (target.matches('tbody [type="checkbox"]')) {
    
        tableNode
          .querySelector('thead [type="checkbox"]')
          .checked = Array
            .from(
              target
                .closest('tbody')
                .querySelectorAll('[type="checkbox"]')
            )
            .every(elmNode => elmNode.checked);
    
        updateTableRowActiveState(target);
      }
      updateCheckboxCounter();
    }
    
    function init() {
      document
        .querySelectorAll('table.table')
        .forEach(elmNode =>
    
          elmNode.addEventListener('change', updateCheckboxDependedStates)
        );
      document
        .querySelectorAll('table.table tbody [type="checkbox"]:checked')
        .forEach(updateTableRowActiveState);
    
      updateCheckboxCounter();
    }
    init();
    .form-check-input {
      border: solid 1px #000;
      position: relative;
    }
    tr.active {
      background-color: lightblue;
    }
    
    body { margin: 0; zoom: .88; }
    p { margin: 0; }
    <div class="container">
      <div class="row">
        <div class="col-12">
    
          <p>Table 1</p>
    
          <table class="table table-sm table-borderless">
            <thead>
              <tr>
                <th><input class="form-check-input" type="checkbox" value=""></th>
                <th>Request date</th>
                <th>Name</th>
                <th>Organisation/Employer</th>
                <th>Selected Course(s)</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td><input class="form-check-input" type="checkbox" value=""></td>
                <td>10/10/2014</td>
                <td><a href="#">Clark Kent</a></td>
                <td><span>Daily Planet</span></td>
                <td><span>Flight</span></td>
              </tr>
              <tr>
                <td><input class="form-check-input" type="checkbox" value=""></td>
                <td>10/10/2014</td>
                <td><a href="#">Hal Jordan</a></td>
                <td><span>Green Lantern Corps</span></td>
                <td>Lighting</td>
              </tr>
              <tr>
                <td><input class="form-check-input" type="checkbox" value="" checked></td>
                <td>10/10/2014</td>
                <td><a href="#">Arthur Curry</a></td>
                <td><span>Atlantis Water</span></td>
                <td>Aquatics</td>
              </tr>
            </tbody>
          </table>
    
          <p>Table 2</p>
    
          <table class="table table-sm table-borderless ">
            <thead>
              <tr>
                <th><input class="form-check-input" type="checkbox" value=""></th>
                <th>Request date</th>
                <th>Name</th>
                <th>Organisation/Employer</th>
                <th>Selected Course(s)</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td><input class="form-check-input" type="checkbox" value="" checked></td>
                <td>10/10/2014</td>
                <td><a href="#">Barry Allen</a></td>
                <td><span>Star Labs</span></td>
                <td><span>Speed</span></td>
              </tr>
              <tr>
                <td><input class="form-check-input" type="checkbox" value=""></td>
                <td>10/10/2014</td>
                <td><a href="#">Bruce Wayne</a></td>
                <td><span>Wayne Enterprises</span></td>
                <td>Combat</td>
              </tr>
            </tbody>
          </table>
    
        </div>
      </div>
    </div>
    
    <p class="output">Total selected: <span>0</span></p>