- Use
data-*
attribute on your checkbox to assign each to a specific family, namely: data-toggle-family
for the "head parent" checkbox and data-family
for the associated checkboxes
- If needed, for a better UI,UX use the
indeterminate
state for the "parent head checkbox" (read more in this CSS Tricks article where examples cover use-cases for tree structure)
Example:
// Parent
$("[data-toggle-family]").on("input", function() {
const family = $(this).data("toggle-family");
const $children = $(`[data-family="${family}"]`);
$children.prop({
checked: this.checked
});
});
// Related children
$("[data-family]").on("input", function() {
const family = $(this).data("family");
const $parent = $(`[data-toggle-family="${family}"]`);
const $siblings = $(`[data-family="${family}"]`);
const $siblingsCkd = $siblings.filter(":checked");
const allChecked = $siblingsCkd.length === $siblings.length;
const allUnchecked = $siblingsCkd.length === 0;
$parent.prop({
checked: allChecked,
indeterminate: !allChecked && !allUnchecked
});
});
// Addon: Make entire parent row clickable
$("tr:has([data-toggle-family])").on("click", function(evt) {
const $parent = $(this).find("[data-toggle-family]");
if ($(evt.target).is($parent)) return;
$parent.prop("checked", !$parent.prop("checked"));
$parent.trigger("input");
});
<table>
<tbody>
<tr>
<th>Header 1</th>
<th><input data-toggle-family="one" type="checkbox"></th>
</tr>
<tr>
<td>Row 1</td>
<td><input data-family="one" type="checkbox"></td>
</tr>
<tr>
<td>Row 2</td>
<td><input data-family="one" type="checkbox"></td>
</tr>
</tbody>
<tbody>
<tr>
<th>Header 2</th>
<th><input data-toggle-family="two" type="checkbox"></th>
</tr>
<tr>
<td>Row 1</td>
<td><input data-family="two" type="checkbox"></td>
</tr>
<tr>
<td>Row 2</td>
<td><input data-family="two" type="checkbox"></td>
</tr>
<tr>
<td>Row 3</td>
<td><input data-family="two" type="checkbox"></td>
</tr>
</tbody>
</table>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
PS:
Not needed for the JS part to work, but remember to assign name="someName"
attribute to your children input elements. If you want you can also rewrite the above to work in relation to that name
attribute:
<input data-toggle-family="one" type="checkbox">
<input name="one" type="checkbox">
<input name="one" type="checkbox">
<input data-toggle-family="two" type="checkbox">
<input name="two" type="checkbox">
<input name="two" type="checkbox">
Not needed for this example, but it's useful to always explicitly use the tbody
element. The browser will create it regardless. You can also create more than one TBODY if needed. Also, you can use the th
element for a better markup syntax — where needed.
Pure JavaScript solution
Nowadays jQuery is not necessary. Here's a remake in JavaScript without any external library:
// DOM utils:
const el = (sel, par) => (par || document).querySelector(sel);
const els = (sel, par) => (par || document).querySelectorAll(sel);
// Parent
els("[data-toggle-family]").forEach(elParent => {
elParent.addEventListener("input", () => {
const family = elParent.dataset.toggleFamily;
const elsChildren = els(`[data-family="${family}"]`);
elsChildren.forEach(elChild => elChild.checked = elParent.checked);
});
});
// Related children
els("[data-family]").forEach(elChild => {
elChild.addEventListener("input", () => {
const family = elChild.dataset.family;
const elParent = el(`[data-toggle-family="${family}"]`);
const elsSiblings = els(`[data-family="${family}"]`);
const siblingsCkd = [...elsSiblings].filter(el => el.checked);
const allChecked = siblingsCkd.length === elsSiblings.length;
const allUnchecked = siblingsCkd.length === 0;
elParent.checked = allChecked;
elParent.indeterminate = !allChecked && !allUnchecked;
});
});
// Addon: Make entire parent row clickable
els("tr:has([data-toggle-family])").forEach(elTr => {
elTr.addEventListener("click", (evt) => {
if (evt.target.closest(`[data-toggle-family]`)) return
const elParent = el(`[data-toggle-family]`, elTr);
console.log(111)
elParent.checked = !elParent.checked;
elParent.dispatchEvent(new Event("input", {bubbles: true}));
});
});
<table>
<tbody>
<tr>
<th>Header 1</th>
<th><input data-toggle-family="one" type="checkbox"></th>
</tr>
<tr>
<td>Row 1</td>
<td><input data-family="one" type="checkbox"></td>
</tr>
<tr>
<td>Row 2</td>
<td><input data-family="one" type="checkbox"></td>
</tr>
</tbody>
<tbody>
<tr>
<th>Header 2</th>
<th><input data-toggle-family="two" type="checkbox"></th>
</tr>
<tr>
<td>Row 1</td>
<td><input data-family="two" type="checkbox"></td>
</tr>
<tr>
<td>Row 2</td>
<td><input data-family="two" type="checkbox"></td>
</tr>
<tr>
<td>Row 3</td>
<td><input data-family="two" type="checkbox"></td>
</tr>
</tbody>
</table>