Tuesday, May 2, 2017

Events for checkboxes and radio buttons



Almost anyone familiar with developing for the Web has worked with events. We use these events to know when things happen in the browser like when a user clicks on something, that they entered data, when the webpage is finished loading or was resized.

Events for user interaction

When a user clicks on a checkbox or radio button the element dispatches a click event. When clicking on a radio button this event does not always indicate a value has changed only that the user has clicked on the element. The click event is also triggered when the user uses their keyboard to select a checkbox or radio button.

When the checked state of a checkbox or radio button changes, due to the click event, then the change event is dispatched. The change is the preferred event to use as an indication that the checked state has changed on an element. Some older browsers dispatch the click event before the checked state changes so that event is not as reliable as the change event.

The sample code below (and here) displays an appropriate message when the user clicks on the checkbox:

<input id="cb10" type="checkbox" value="10"> Ten<br>
<hr>
<div id="output"></div>
<script>
var el = document.getElementById('cb10');
var out = document.getElementById('output');

function changeHandler(event) {
  if (event.target.checked) {
    out.textContent = 'Ten is checked.';
  }
  else {
    out.textContent = 'Ten is not checked.';
  }
}

el.addEventListener('change', changeHandler);
</script>

The Javascript will display the message 'Ten is checked.' or 'Ten is not checked.' based on the current state of the checkbox.

The checked property of an element

The checkbox and radio button DOM elements have a checked property that indicates the current checked state. The checked property is a boolean value.

Within your JavaScript you can set the checked value and the state of the checkbox or radio button will change.

<input id="cb10" type="checkbox" value="10"> Ten<br>

<script>
var el = document.getElementById('cb10');
el.checked = true;
</script>

When you set the checked property the DOM is not changed. The checked attribute is only used to define the initial state of the checked property.

After the DOM is rendered the first time setting the property does not affect the attribute and setting the attribute no longer affects the property.

CSS can use either the property or the attribute

CSS is designed to support both the property value and the attribute by using the :checked selector and the [checked] selector.

:checked is based on the property value and [checked] is based on the existence of the checked attribute.

The following CSS will draw a green box around the checkbox when the checked property is set to true:

input:checked {
  outline: 3px solid green;
}

And this CSS will draw a red box around the checkbox when the checked attribute exists in the DOM:

input[checked] {
  outline: 3px solid red;
}
But avoid the attribute selector
It is best to avoid using the attribute selector since the attribute does not change when the user selects the checkbox. But the :checked selector does recognize anytime the checked property has changed. If you want to manage the attribute yourself, then the attribute selector might work for you.


Setting the checked property does not trigger an event

This is something that is not well understood. But when you set the checked property in JavaScript an event is not dispatched. Which means that if your code expects to always get an event when the checked state of an element has changed you will only get the event when the user interacts with the element and not when the JavaScript changes the property.

This example shows that setting element.checked does not trigger an event. Line 18 sets element.checked to true or false based on the button that was clicked.



My solution


I provide two examples below that allow your JavaScript to emulate the events that are sent by default through user interaction.

The first example sends the click event. Then the browser automatically sends the change event. This acts most like the real user interaction.


function setChecked(el, state) {
  el.checked = !state;
  el.dispatchEvent(new MouseEvent('click'));
}

The second example only triggers the change event. If if all you are listening for is the change event then this might be the better option. It is also slightly faster since only one event is dispatched.


function setChecked(el, state) {
  el.checked = state;
  el.dispatchEvent(new Event('change’));
}

Either example will help to resolve the issue of events not being triggered when setting the checked property. But you have to make sure that all code calls the helper function instead of setting the checked property directly.

In this example I use the setChecked function and events fire like I expect and want.