Consider the following HTML snippet: This is basically a nested group of checkboxes, using unordered list markup to create a general grouping. The code is being generated dynamically so I have complete control over the markup as needed.
A jsfiddle representing the problem below is at: http://jsfiddle.net/bwh4rj3h/
I am attempting to create jQuery that performs the following actions:
- Clicking Select All toggles the state of all the checkboxes in the snippet.
- Clicking on a checkbox deeper in the matrix will toggle all "sub-checkboxes". e.g. clicking on the Group1 checkbox will toggle the state of all the checkboxes in the construct underneath the group.
- If any checkboxes are unchecked in given group, then the group checkbox must also be deselected (While technically a tri-state checkbox representing a "partial" fill would be nice, that's beyond the scope of my question). e.g. Say item 1-1, 1-2, and 1-3 are all checked. Then group 1 should be checked, and in this example the "Select All" should also be checked. However if I deselect item 1-2, now group 1 is not "complete" and so should be unchecked, as should the "Select All".
OK, so I have some jQuery code that works for the markup below. It works fine. But it's ugly. And I hate ugly and feel like there's a flaw in my logic, selectors, and/or HTML markup that's causing things to be this ugly and that there IS A BETTER WAY.
Would love to get some feedback as to what that better way is. In my personal case, I don't have to worry about more than 2 levels deep, but a generic N-levels deep functionality shouldn't be too hard. In fact, if it weren't for the 3rd condition where I'm checking the state of "children" to see if everything is checked/unchecked, it would've been relatively straightforward. The fact that I'm doing a 3 time loop (because of order of evaluation) makes me sad.
Your assistance is appreciated in advance. :)
You'll notice that I am using a :disabled check. In my final project, I am setting some checkboxes to be disabled and as such they are "immune" from my 3 requirements listed above. Since the markup doesn't include any disabled items, this should not affect the outcome.
<ul>
<li>
<input type="checkbox" id="chkAll" checked/><label for="chkAll">Select All</label>
</li>
<ul>
<li>
<input type="checkbox" id="chkGroup1" checked/><label for="chkGroup1">Group 1</label>
</li>
<ul>
<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>
</ul>
</ul>
The jQuery:
$("input[type=checkbox]:not(:disabled)").on("change", function () {
$(this).parent().next("ul").find("li input[type=checkbox]:not(:disabled)").prop("checked", $(this).prop("checked"));
// Looping 3 times to cover all sublevels. THERE HAS GOT TO BE A BETTER WAY THAN THIS.
for (var i = 0; i < 3; i++) {
$("input[type=checkbox]:not(:disabled)").each(function() {
var uncheckedkidcount = $(this).parent().next("ul").find("li input[type=checkbox]:not(:disabled):not(:checked)").length;
var kidcount = $(this).parent().next("ul").find("li input[type=checkbox]:not(:disabled)").length;
if (kidcount > 0) {
$(this).prop("checked", uncheckedkidcount == 0);
}
});
}
});
tag within a- tag. I was wondering about that at the time, so thanks for the link. I'm going to leave this answer up for a couple more days (I didn't see when you answered it, so I apologize... I thought I would get an email notification) and in lieu of something superior, will accept this.
Thanks so much for taking the time, grammar. I just spent an hour answering another question for someone else... Stack Overflow is a good thing!
– Vincent Polite Sep 15 '14 at 15:28