Reputation: 67
I am new to JS and I have a page with 2 filters, I am stuck and I am trying to:
check automatically the (All) checkbox if every other checkbox of the filter are checked.
when a user unchecked a checkbox, automatically uncheck the (All) checkbox.
I am trying the following code but it is not really working...
HTML:
<div id="filter1">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)
<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F1: Value1
<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F1: Value2
<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F1: Value3
<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F1: Value4
<br>
<input type="submit" value="Apply">
</form>
</div>
<div id="filter2">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)
<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F2: Value1
<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F2: Value2
<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F2: Value3
<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F2: Value4
<br>
<input type="submit" value="Apply">
</form>
</div>
JS:
$(".checkboxlistitem").change(function() {
$(".select-all").prop("checked", $(".checkboxlistitem:checked").length == $(".checkboxlistitem").length);});
JSFIDDLE:
https://jsfiddle.net/Max06270/5scgbnww/
Let me know if you need clarifications, Thanks in advance, Max
Upvotes: 2
Views: 9829
Reputation: 1094
const checkboxes = document.getElementsByClassName("checkboxlistitem");
const selectAllBtn = document.getElementById("select-all");
for (let i = 0; i < checkboxes.length; i++) {
checkboxes[i].addEventListener("change", () => {
for (let i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked === false) {
selectAllBtn.checked = false;
break;
} else {
selectAllBtn.checked = true;
}
}
});
}
// select all button
selectAllBtn.addEventListener("change", (event) => {
for (let i = 0, n = checkboxes.length; i < n; i++) {
checkboxes[i].checked = event.target.checked;
}
});
<div id="filter1">
<form action='#'>
<input type="checkbox" value="all" id="select-all" checked>(All)<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F1: Value1<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F1: Value2<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F1: Value3<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F1: Value4<br>
<input type="submit" value="Apply">
</form>
</div>
Upvotes: 0
Reputation: 179046
Fixing the form you have:
Your selectors of $('.checkboxlistitem')
and $('.checkboxlistitem:checked')
select all elements with those classes, and are therefor selecting the checkboxes in both forms. To fix this they need context.
Within jQuery's event callbacks, this
refers to the element that the event is fired on. That means that we can use relative selectors to find the appropriate elements:
// find the nearest ancestor that's a form
$form = $(this).closest('form');
// find all the `.checkboxlistitem` elements within the form
$checkboxes = $form.find('.checkboxlistitem');
// find the all checkbox within the form
$all = $form.find('.select-all');
// if there are any unchecked checkboxes the all option should be unchecked
$all.prop('checked', !$checkboxes.not(':checked').length);
// leaving this as it was in the jsfiddle
// Select/Unselect all chexboxes of the form
$(".select-all").change(function () {
$(this).siblings().prop('checked', $(this).prop("checked"));
});
$('.checkboxlistitem').change(function () {
var $form,
$checkboxes,
$all;
// find the nearest ancestor that's a form
$form = $(this).closest('form');
// find all the `.checkboxlistitem` elements within the form
$checkboxes = $form.find('.checkboxlistitem');
// find the all checkbox within the form
$all = $form.find('.select-all');
// if there are any unchecked checkboxes the all option should be unchecked
$all.prop('checked', !$checkboxes.not(':checked').length);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filter1">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)
<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F1: Value1
<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F1: Value2
<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F1: Value3
<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F1: Value4
<br>
<input type="submit" value="Apply">
</form>
</div>
<div id="filter2">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)
<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F2: Value1
<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F2: Value2
<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F2: Value3
<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F2: Value4
<br>
<input type="submit" value="Apply">
</form>
</div>
This is great and all, but lets take it up a notch by improving the accessibility of your form and the behavior of partially complete checkbox lists.
As-is your checkboxes have no labels.
You added text after them, certainly, but a screen reader has no way of knowing what each checkbox represents.
To fix this, use <label>
elements.
<form action="#">
<label>
<input type="checkbox" value="all" class="select-all" checked>
(All)
</label>
<br>
<label>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>
F1: Value1
</label>
<br>
...
</form>
They can either have the checkbox nested or have their [for]
attributes point to the [id]
attributes of the <input>
elements, or both.
<form action="#">
<input type="checkbox" id="f1-all" value="all" class="select-all" checked> <label for="f1-all">(All)</label>
<br>
<input type="checkbox" id="f1-v1" value="v1" class="checkboxlistitem" checked> <label for="f1-v1">F1: Value1</label>
<br>
...
</form>
There's a different hierarchical relationship between the "all" checkbox and the individual F#: Value#
checkboxes.
To fix this, use <fieldset>
and <legend>
elements.
<form action="#">
<fieldset>
<legend>
<label>
<input type="checkbox" value="all" checked>
<span>Select All in Filter 1</span>
</label>
</legend>
<label>
<input type="checkbox" value="v1" checked>
<span>F1: Value1</span>
</label>
<br>
...
</fieldset>
</form>
You're currently using <br>
elements to separate the fields onto different lines.
<form action="#">
<fieldset>
<legend>...</legend>
<p>
<label>
<input type="checkbox" value="v1" checked>
<span>F1: Value1</span>
</label>
</p>
...
</fieldset>
</form>
You're using the [value]
attributes, which is good, but none of the <input>
elements have [name]
attributes, which means that the data won't be sent to the server, and calling jQuery's .serialize()
on the forms won't get you the appropriate data.
Set the [name]
on the checkboxes that have important values (the "all" checkbox won't be necessary):
<form action="#">
<fieldset>
<legend>...</legend>
<p>
<label>
<input type="checkbox" name="v1" value="1" checked>
<span>F1: Value1</span>
</label>
</p>
...
</fieldset>
</form>
Alternatively, you can set all the [name]
attributes to filter
and rely on the various values to produce query strings such as filter=v1&filter=v2
, but this is an advanced technique that's likely going to run into bugs with querystring parsing, so proceed with caution.
While it's nice to be able to automatically uncheck the "all" checkbox, when some checkboxes are checked and other checkboxes are unchecked the unchecked state doesn't exactly make sense for the "all" checkbox. It's not at "none" and it's not at "all".
Enter the indeterminate state.
Checkboxes have a third state that's only accessible from JavaScript that was designed for exactly this situation.
You can set a given checkbox to the indeterminate state by toggling its indeterminate
property:
document.querySelector('input[type="checkbox"]').indeterminate = true;
or with jQuery:
$('input[type="checkbox"]').first().prop('indeterminate', true);
Checking whether the list is in an indeterminate state can be done by differentiating between:
In practice this might look something like:
checkedCount = $checkboxes.filter(':checked').length;
if (!checkedCount) {
// no checkboxes checked
$all.prop({
checked: false,
indeterminate: false
});
} else if (checkedCount === $checkboxes.length) {
// all checkboxes checked
$all.prop({
checked: true,
indeterminate: false
});
} else {
// some checkboxes checked and unchecked
$all.prop({
indeterminate: true
});
}
A fully fleshed out implementation of all of the above looks like:
$('.filter').on('change', '[value="all"]', function () {
var $form,
$checkboxes,
$all;
$form = $(this).closest('form');
$checkboxes = $form.find('[name="filter"]');
$all = $form.find('[value="all"]');
$checkboxes.prop('checked', $all.prop('checked'));
}).on('change', '[name="filter"]', function () {
var $form,
$checkboxes,
$all,
checkedCount;
$form = $(this).closest('form');
$checkboxes = $form.find('[name="filter"]');
$all = $form.find('[value="all"]');
checkedCount = $checkboxes.filter(':checked').length;
if (!checkedCount) {
// no checkboxes checked
$all.prop({
checked: false,
indeterminate: false
});
} else if (checkedCount === $checkboxes.length) {
// all checkboxes checked
$all.prop({
checked: true,
indeterminate: false
});
} else {
// some checkboxes checked and unchecked
$all.prop({
indeterminate: true
});
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filter1" class="filter">
<form action='#'>
<fieldset>
<legend>
<label>
<input type="checkbox" value="all" checked>
<span>Select All in Filter 1</span>
</label>
</legend>
<p>
<label>
<input type="checkbox" name="filter" value="v1" checked>
<span>F1: Value1</span>
</label>
</p>
<p>
<label>
<input type="checkbox" name="filter" value="v2" checked>
<span>F1: Value2</span>
</label>
</p>
<p>
<label>
<input type="checkbox" name="filter" value="v3" checked>
<span>F1: Value3</span>
</label>
</p>
<p>
<label>
<input type="checkbox" name="filter" value="v4" checked>
<span>F1: Value4</span>
</label>
</p>
</fieldset>
<input type="submit" value="Apply">
</form>
</div>
<div id="filter2" class="filter">
<form action='#'>
<fieldset>
<legend>
<label>
<input type="checkbox" value="all" checked>
<span>Select All in Filter 2</span>
</label>
</legend>
<p>
<label>
<input type="checkbox" name="filter" value="v1" checked>
<span>F2: Value1</span>
</label>
</p>
<p>
<label>
<input type="checkbox" name="filter" value="v2" checked>
<span>F2: Value2</span>
</label>
</p>
<p>
<label>
<input type="checkbox" name="filter" value="v3" checked>
<span>F2: Value3</span>
</label>
</p>
<p>
<label>
<input type="checkbox" name="filter" value="v4" checked>
<span>F2: Value4</span>
</label>
</p>
</fieldset>
<input type="submit" value="Apply">
</form>
</div>
Upvotes: 2
Reputation: 2750
You're jquery selectors were too generic, so they were affecting both forms. Below should be a working example of what you want:
$(".select-all").change(function () {
$(this).siblings().prop('checked', $(this).prop("checked"));
});
$(".checkboxlistitem").change(function() {
var checkboxes = $(this).parent().find('.checkboxlistitem');
var checkedboxes = checkboxes.filter(':checked');
if(checkboxes.length === checkedboxes.length) {
$(this).parent().find('.select-all').prop('checked', true);
} else {
$(this).parent().find('.select-all').prop('checked', false);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filter1">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F1: Value1<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F1: Value2<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F1: Value3<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F1: Value4<br>
<input type="submit" value="Apply">
</form>
</div>
<br>
<div id="filter2">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F2: Value1<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F2: Value2<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F2: Value3<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F2: Value4<br>
<input type="submit" value="Apply">
</form>
</div>
https://jsfiddle.net/0epu6Lnn/
Upvotes: 5