Why does toggling a checkbox/radiobutton with JavaScript fail after calling event.preventDefault()?

Given your HTML:

<span>
    <input type="checkbox">
</span>

and your code:

$('span').click(function(e) {
    e.preventDefault();
    $(':checkbox')[0].checked = true;
});

Event bubbling is your issue here.

When clicking the checkbox, the click event is propagated to the span. e.preventDefault() is the called on the event object propagated from the checkbox.

Hence preventDefault() is execute against the checkbox itself, preventing it from being checked.

Ones the click event of the checkbox is "cancelled" by means of preventDefault it will stay cancelled until completion of the event flow. (See bottom of answer for more details on that)

If you apply some styling to the span you notice that clicking around the checkbox your code works as expected but clicking on the checkbox itself replicates your issue due to the above mentioned.


DEMO - Original Fiddle + styles. Clicking span is fine, checkbox is not


According to the MDN documentation on preventDefault():

Calling preventDefault during any stage of event flow cancels the event, meaning that any default action normally taken by the implementation as a result of the event will not occur.

The DOM Level 2 Spec also notes:

If an event is cancelable, the preventDefault method is used to signify that the event is to be canceled, meaning any default action normally taken by the implementation as a result of the event will not occur. If, during any stage of event flow, the preventDefault method is called the event is canceled.

The DOM Level 3 Events Spec notes under Example 5:

The default action associated with the click event on <input type="checkbox"> elements toggles the checked IDL attribute value of that element. If the click event's default action is cancelled, then the value is restored to its former state.


Wrapping part of the code in a setTimeout does the trick, as you can see here http://jsfiddle.net/y7C77/

$('span').click(function(e) {
    e.preventDefault();
    setTimeout(function () {
        $('input').prop('checked', true);
    }, 1);
});

preventDefault() cancels the default action, and I think that it's able to cancel it even if you manually do what the browser would do (in this case: altering the checked property).


This is the expected behavior what I feel since, when you say event.preventDefault() you are instructing the browser not to do any operation on it in this event. So, even after the statement when you explicitly check the checkbox, browser will not do any operation on it. We can see the difference when I change the event as follows:

$('span').click(function(e) {
     e.preventDefault();
     //$(':checkbox')[0].checked = true;
     setTimeout(function(){$(':checkbox')[0].checked = true;});
});

Using timeout we are changing the checked property outside the event, hence its get checked.