3

I've used the following codepen snippet to implement nested checkboxes into my HTML page. The original code only catered for one set of nested checkboxes so I've tried to expand to multiple. However, it seems to work for the last Main element in this case Main2 but not the previous ones.

function loadExpandableCheckboxes() {
 
 var expandableCheckboxes = document.getElementsByClassName("expandable-checkbox");
 for (var i = 0; i < expandableCheckboxes.length; i++) {
  
  var checkboxMainOption = expandableCheckboxes[i].getElementsByClassName("expandable-checkbox-main-option")[0];
  
  var checkboxSubOptions = expandableCheckboxes[i].getElementsByClassName("expandable-checkbox-sub-option");
  for (var j = 0; j < checkboxSubOptions.length; j++) {
   
   checkboxSubOptions[j].onclick = function() {
    
    var checkedCount = 0;
    for (var k = 0; k < checkboxSubOptions.length; k++) {
     if (checkboxSubOptions[k].checked) {
      checkedCount++;
     }
    }
    
    checkboxMainOption.checked = checkedCount > 0;
    checkboxMainOption.indeterminate = checkedCount > 0 && checkedCount < checkboxSubOptions.length;
   }
  }
  
  checkboxMainOption.onclick = function() {
   
   for (var j = 0; j < checkboxSubOptions.length; j++) {
    checkboxSubOptions[j].checked = checkboxMainOption.checked;
   }
  }
 }
}
onload=loadExpandableCheckboxes;
body {
  color: #555;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

ul {
  list-style: none;
}

li {
  margin-top: 1em;
}

label {
  font-weight: bold;
}
<html>
  <head>
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
  </head>
  <body>
    <div class="checkbox-container">
      <ul>
        <li>
    <div class="expandable-checkbox">
            <label><input type="checkbox" class="expandable-checkbox-main-option">Main 1</label>
            <ul>
              <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 1 Sub 1</label></li>
              <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 1 Sub 2</label></li>
              <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 1 Sub 3</label></li>
            </ul>
    </div>
        </li>
        <li>
    <div class="expandable-checkbox">
            <label><input type="checkbox" class="expandable-checkbox-main-option">Main 2</label>
            <ul>
              <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 2 Sub 1</label></li>
              <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 2 Sub 2</label></li>
              <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 2 Sub 3</label></li>
            </ul>
    </div>
        </li>
      </ul>
    </div>
   
  </body>
</html>
TheLethalCoder
  • 6,875
  • 6
  • 31
  • 58

2 Answers2

4

This was a fun one. So basically to stop from overwriting the first set of checkboxes with the second set, you need to use a temporary scope to preserve the variables. In the snippet below, you'll see that I added an IFFE to your i loop.

What this does is take the variable i, pass it into the temporary function which calls itself immediately. Next time the loop runs, a new temporary function is "created", thus preserving the scope of the last one, and not overwriting the previous checkbox sets.

$(document).ready(function() {
  //find the wrappers
  var expandableCheckboxes = document.getElementsByClassName("expandable-checkbox");
  //loop through the wrappers, count them one by one
  for (var i = 0; i < expandableCheckboxes.length; i++) (function(i){
  
    //find the the main
    var checkboxMainOption = expandableCheckboxes[i].getElementsByClassName("expandable-checkbox-main-option")[0];
    //find the subs
    var checkboxSubOptions = expandableCheckboxes[i].getElementsByClassName("expandable-checkbox-sub-option");
    //loop through subs
    for (var k = 0; k < checkboxSubOptions.length; k++) {
      
      //add listener fo each sub
      checkboxSubOptions[k].onclick = function() {
        var checkedCount = 0;
        for (var j = 0; j < checkboxSubOptions.length; j++) {
          if (checkboxSubOptions[j].checked) {
            checkedCount++;
          }
        }
        //mark appropriately
        checkboxMainOption.checked = checkedCount > 0;
        checkboxMainOption.indeterminate = checkedCount > 0 && checkedCount < checkboxSubOptions.length;
      }
    }
    //add listener to main
    checkboxMainOption.onclick = function() {
      for (var j = 0; j < checkboxSubOptions.length; j++) {
        checkboxSubOptions[j].checked = checkboxMainOption.checked;
      }
    }
  })(i)
});
body {
  color: #555;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

ul {
  list-style: none;
}

li {
  margin-top: 1em;
}

label {
  font-weight: bold;
}
<html>

<head>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
</head>

<body>
  <div class="checkbox-container">
    <ul>
      <li>
        <div class="expandable-checkbox">
          <label><input type="checkbox" class="expandable-checkbox-main-option">Main 1</label>
          <ul>
            <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 1 Sub 1</label></li>
            <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 1 Sub 2</label></li>
            <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 1 Sub 3</label></li>
          </ul>
        </div>
      </li>
      <li>
        <div class="expandable-checkbox">
          <label><input type="checkbox" class="expandable-checkbox-main-option">Main 2</label>
          <ul>
            <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 2 Sub 1</label></li>
            <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 2 Sub 2</label></li>
            <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 2 Sub 3</label></li>
          </ul>
        </div>
      </li>
    </ul>
  </div>

</body>

</html>
amflare
  • 3,602
  • 3
  • 20
  • 39
3

If you don't mind using JQuery, you can shorten your JS here. Try the snippet below:

$(document).ready(function() {
  $('.expandable-checkbox-main-option').change(function() {
    $(this).parent().parent('div').find('.expandable-checkbox-sub-option').prop('checked', $(this).prop('checked'));
  });

  $('.expandable-checkbox-sub-option').change(function() {
    var parentCheckboxDiv = $(this).parent().parent().parent().parent('div');
    var parentCheckbox = parentCheckboxDiv.find('.expandable-checkbox-main-option');
    var checkedSubOptions = parentCheckboxDiv.find('.expandable-checkbox-sub-option:checked');
    var subOptions = parentCheckboxDiv.find('.expandable-checkbox-sub-option');

    parentCheckbox.prop('indeterminate', checkedSubOptions.length && checkedSubOptions.length != subOptions.length);
    parentCheckbox.prop('checked', checkedSubOptions.length == subOptions.length)
  });
});
body {
  color: #555;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

ul {
  list-style: none;
}

li {
  margin-top: 1em;
}

label {
  font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="checkbox-container">
  <ul>
    <li>
      <div class="expandable-checkbox">
        <label><input type="checkbox" class="expandable-checkbox-main-option">Main 1</label>
        <ul>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 1 Sub 1</label></li>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 1 Sub 2</label></li>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 1 Sub 3</label></li>
        </ul>
      </div>
    </li>
    <li>
      <div class="expandable-checkbox">
        <label><input type="checkbox" class="expandable-checkbox-main-option">Main 2</label>
        <ul>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 2 Sub 1</label></li>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 2 Sub 2</label></li>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 2 Sub 3</label></li>
        </ul>
      </div>
    </li>
    <li>
      <div class="expandable-checkbox">
        <label><input type="checkbox" class="expandable-checkbox-main-option">Main 3 (5 options)</label>
        <ul>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 3 Sub 1</label></li>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 3 Sub 2</label></li>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 3 Sub 3</label></li>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 3 Sub 4</label></li>
          <li><label><input type="checkbox" class="expandable-checkbox-sub-option">Main 3 Sub 5</label></li>
        </ul>
      </div>
    </li>
  </ul>
</div>
Dan Kreiger
  • 4,585
  • 2
  • 17
  • 23