JSFiddle - React, Tailwind, and code Playground
by grammar
HTML
<ul>
<li>
<input type="checkbox" id="chkAll" checked/>
<label for="chkAll">Select All</label>
</li>
<li class="has-nested-list">
<ul class="nested">
<li class="has-nested-list">
<input type="checkbox" id="chkGroup1" class="group-leader" checked/>
<label for="chkGroup1">Group 1</label>
<ul class="nested">
<li>
<input type="checkbox" id="chkGp1-Item1" checked/>
<label for="chkGp1-Item1">Item 1</label>
</li>
<li>
<input type="checkbox" id="chkGp1-Item2" checked/>
<label for="chkGp1-Item2">Item 2</label>
</li>
</ul>
</li>
</ul>
</li>
<li class="has-nested-list">
<ul class="nested">
<li>
<input type="checkbox" id="chkGroup2" class="group-leader" checked/>
<label for="chkGroup2">Group 2</label>
<ul class="nested">
<li>
<input type="checkbox" id="chkGp2-Item1" checked/>
<label for="chkGp2-Item1">Item 1</label>
</li>
<li>
<input type="checkbox" id="chkGp2-Item2" checked/>
<label for="chkGp2-Item2">Item 2</label>
</li>
<li>
<input type="checkbox" id="chkGp2-Item3" checked/>
<label for="chkGp2-Item3">Item 3</label>
</li>
</ul>
</li>
</ul>
</li>
</ul>
CSS
/* to get rid of double bullets */
.has-nested-list {
list-style-type: none;
}
JavaScript
$( function() {
// Store the checkboxes to manage scope
var $allCheckboxes = $('ul input[type=checkbox]'),
// Store a quick reference to the 'check-all' button for comparison
$checkAllCheckbox = $allCheckboxes.filter('#chkAll'),
// A cache of all the checkboxes except the check-all
// Useful when altering the check-all
$allNestedCheckboxes = $allCheckboxes.not( $checkAllCheckbox );
// Listen to change event on all checkboxes
$allCheckboxes.on( 'change', function( event ) {
// Store what changed and what it changed to
var $this = $(this),
value = $this.prop('checked'),
// Because val returns true or false, we have to store
// something that the input tag understands
checkedValue = value ? 'checked' : '',
// Placeholder for parent and children elements
$parent, $children;
// If this was the 'check-all' checkmark, then simply alter all
if( $this.is( $checkAllCheckbox ) ) {
$allCheckboxes.each( function( idx, item ) {
$(item).prop( 'checked', value );
// Reset disabled property just in case
$(item).prop( 'disabled', '' );
});
// Otherwise, check if this was a 'group-leader' alter the checkboxes
// inside its nested list
} else if( $this.hasClass('group-leader') ) {
// Only works with one level of nesting at this time
$children = $this.siblings('ul').find('input[type=checkbox]');
$children.each( function( idx, item ) {
$(item).prop( 'checked', checkedValue );
});
// Otherwise, it was a singular checkbox, which affects only its parent
// 'group-leader', which must be checked when all siblings are checked,
// 'disabled' when some are checked, and unchecked when none are...