I feel like an idiot for this amount of time I spent on this one today at work.
TL:DR; What you're looking for is:
// <input type="checkbox" id="your-checkbox-id" />
const yourCheckbox = document.getElementById('your-checkbox-id');
// check the checkbox
yourCheckbox.checked = true;
// uncheck the checkbox
yourCheckbox.checked = false;
So what was I doing wrong today? Let's take a look at the Codepen below.
❌ Using the setAttribute method
I had a list of checkboxes, with one "select all" checkbox at the top. When the "select all" checkbox is checked, I wanted to check all the underlying checkboxes.
I tried setting the checked
attribute on the input elements. My code looked something like this:
const selectAllCheckbox = document.getElementById('select-all');
const childCheckboxes = document.querySelectorAll('.child-checkbox');
const toggleSelectAllCheckbox = () => {
const areAllChecked = [...childCheckboxes].every(c => c.hasAttribute('checked'));
if (areAllChecked) {
// uncheck all if every checkbox is checked
childCheckboxes.forEach(c => {
c.removeAttribute('checked');
})
}
else {
// otherwise, check all
childCheckboxes.forEach(c => {
c.setAttribute('checked', '');
})
}
}
selectAllCheckbox.addEventListener('click', toggleSelectAllCheckbox);
This seems alright at first glance. If you toggle the "select all" checkbox it works.
However, if you toggle any of the child checkboxes then toggle "select all", it ignores the children you just toggled. Try it out in the Codepen example above. This won't do.
✅ Setting the "checked" property
Eventually, I figured out using the setAttribute()
and removeAttribute()
methods was where I was going wrong. Instead, I needed to access and set the checked
property directly, like so:
const selectAllCheckbox = document.getElementById('select-all');
const childCheckboxes = document.querySelectorAll('.child-checkbox');
const toggleSelectAllCheckbox = () => {
const areAllChecked = [...childCheckboxes].every(c => c.checked === true)
childCheckboxes.forEach(c => {
c.checked = !areAllChecked;
})
}
selectAllCheckbox.addEventListener('click', toggleSelectAllCheckbox);
There is some extra work to make sure the "select all" checkbox updates when toggling the children, but you can check out the Codepen for that.
Why was I wrong?
When it comes to setting attributes on HTML elements, I had always assumed element.setAttribute('attribute', value)
was roughly interchangeable with element.attribute = value
. But there is a subtle difference.
HTML attributes have two sides, the content attribute and the IDL attribute.
The content attribute is what is written in HTML. It is also accessed via the methods element.hasAttribute()
, element.getAttribute()
, element.setAttribute()
, and element.removeAttribute()
.
For example, if I have <input type="checkbox" id="your-checkbox-id" />
, checkboxElement.hasAttribute('checked')
will be false.
If I then set checkboxElement.setAttribute('checked')
and use a devtools inspector, my HTML will now be <input type="checkbox" id="your-checkbox-id" checked />
.
The IDL attribute is a JavaScript property. If I log my element to the console with console.log('checkboxElement')
, I can drill into it like any JavaScript object and see all the properites. These are the IDL attributes.
The IDL attribute will use the underlying content attribute. element.setAttribute("attribute", value)
and element.attribute = value
are interchangable...most of the time.
The catch with checkboxes is that, for the checked
IDL attribute, the underlying content attribute is not checked
. It doesn't have one.
The content attribute checked
for inputs with type="checkbox"
instead corresponds to the defaultChecked
IDL attribute. Even though element.setAttribute("checked")
sort of works for programatically checking a checkbox (at least in the browsers I tried), it's not supposed to.
Use the checked
content attribute if you want the checkbox checked by default at page load (or when the element is first painted).
If you want to mutate the checkbox with JavaScript, use the checked
IDL attribute: element.checked = true
.
Top comments (0)