Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Consider this sample code:

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

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

Fiddle

From my knowledge, this should happen:

  • preventDefault() should prevent the checkbox from being checked by the browser's default behavior, even if the event handler is attached above in the DOM hierarchy. This part works correctly.
  • Setting .checked = true should work as, I believe, it should be independent of the browser's default action for the event which I've cancelled. This part seems buggy, as if the preventDefault() was affecting it -- remove the preventDefault() and it works as intended.

What's the actual reason why the checkbox stays always unchecked?

I've tested on Chrome 33 and Firefox 27, so this doesn't seem to be a browser bug.

This question is mostly due to curiosity to extend my DOM/Event model knowledge. I don't want workarounds, but rather I want to know why this example fails.

like image 643
Fabrício Matté Avatar asked Feb 25 '14 13:02

Fabrício Matté


People also ask

Why does preventDefault () on a parent element's click disable a checkbox?

The preventDefault() method cancels the event if it is cancelable, meaning that the default action that belongs to the event will not occur. For example, this can be useful when: Clicking on a "Submit" button, prevent it from submitting a form.

What does event preventDefault () do?

Event.preventDefault() The preventDefault() method of the Event interface tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be.

What is the opposite of event preventDefault ()?

There is no opposite method of event. preventDefault() to understand why you first have to look into what event. preventDefault() does when you call it. Underneath the hood, the functionality for preventDefault is essentially calling a return false which halts any further execution.


2 Answers

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.

like image 133
Nope Avatar answered Oct 23 '22 04:10

Nope


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).

like image 35
Alessandro Vendruscolo Avatar answered Oct 23 '22 05:10

Alessandro Vendruscolo