10

I encountered this situation recently (simplified here). Simply wrap a checkbox with an element and apply preventDefault() on it's click event and the checkbox becomes uncheckable.

See this fiddle, but here's a snip:

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

/* Wrapper could be any element (and any ancestor element will work) */
$('div').on('click', function(e){
    e.preventDefault();
});


/* Uncomment to make the checkbox work again 
$('input').on('click', function(e){
    e.stopPropagation();
});
*/

The behavior occurs in Chrome and FF, so I assume it is intentional.

Why does the click event, which has already been triggered on the checkbox itself not result in the checkbox getting toggled? The preventDefault on the ancestor seems like it ought to be irrelevant to the child checkbox's behavior. It seems as if, for the checkbox change to occur, the click event needs to bubble freely all the way to the document root.

What's going on here?

peteorpeter
  • 4,037
  • 2
  • 29
  • 47
  • 5
    This is the intended behavior of `preventDefault()`. It doesn't matter what node the eventObject is bubbling through, it's still the same object. – Brad M Apr 02 '13 at 14:46
  • Funny thing is that you actually provided the right answer :-) For people more acknowledged in event handling, this is a quick one :-) – jave.web Apr 24 '16 at 20:06

2 Answers2

13

The preventDefault on the ancestor seems like it ought to be irrelevant to the child checkbox's behavior.

No, not really. The event bubbles, and all handlers (including the ones on ancestors) may affect it.

It seems as if, for the checkbox change to occur, the click event needs to bubble freely all the way to the document root.

Not exactly. It doesn't need to arrive at the document root, but it must not have been default-prevented.

You might want to read the architecture notes in the DOM spec on event flow, default actions and cancelable events.

So what does happen step-by-step?

  1. You click on the checkbox
  2. It gets checked
  3. The event is dispatched on the document root
  4. (Not in IE): capture phase, nothing happens with your handlers
  5. The event arrives at the <input>
  6. …and begins to bubble
  7. On the <div>, it is handled. Your event listener calls the preventDefault method, setting an internal cancelled flag.
  8. It bubbles on, but nothing happens any more.
  9. Since the event was cancelled, the default action should not occur and the checkbox is reset to its previous state.

If you uncomment the second part of your code, steps 6+ look different:

  1. The event is handled on the <input>. Your listener calls the stopPropagation method
  2. …which leads to skipping the bubbling phase. (The div-listener will never be called)
  3. The default action was not prevented, and the checkbox stays checked.
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    Thank you for providing such a simple explanation. "It doesn't need to arrive at the document root, but it must not have been default-prevented" nailed it – Ian Apr 02 '13 at 15:07
  • Would you call Explosion Pills' fiddle as a Google Chrome bug? – Fabrício Matté Apr 02 '13 at 15:10
  • 1
    +1 though Chrome fails to prevent the default action for the radio and IE8 fails both checkbox and radio [here](http://jsfiddle.net/R7dgW/). – Fabrício Matté Apr 02 '13 at 15:11
  • To expand a bit more on what Bergi is saying, the reason why the `span` and `button` events work is because that is during the pre-activation phase and the event listeners must be handled. Default behavior is still canceled: http://jsfiddle.net/P8Fap/4/ – Explosion Pills Apr 02 '13 at 15:19
  • 1
    @FabrícioMatté: Yes, [this](http://jsfiddle.net/R7dgW/) looks like a bug to me. Both FF and Opera do not check the radio button. Notice that Chrome does it correctly if [at least one (other) radio button is checked](http://jsfiddle.net/R7dgW/3/). – Bergi Apr 02 '13 at 15:21
  • 1
    Thanks. And I just noticed that the fiddle would never work in IE8 as IE<9 does not support `addEventListener`, but by adding the old IE feature detection the default action is prevented as expected in IE8. http://jsfiddle.net/R7dgW/8/show – Fabrício Matté Apr 02 '13 at 17:01
  • I was hoping to end up with some reading material when I asked the question and OP delivered. Thanks for an authoritative answer! (Has anyone rewritten the HTML spec as a metaphorical sci-fi novel yet?!?) – peteorpeter Apr 02 '13 at 17:17
5

preventDefault prevents the browser's default action from being fired. Changing the checked state of a checkbox or following the href of an anchor, submitting a form, etc., are default actions. preventDefault prevents them, while allowing event propagation.

This is distinct from event propagation, (which you may – as you've noted – stop by stopPropagation). The event will propagate throughout the entire hierarchy of listeners before invoking the browser's default behavior (unless prevented).

David Hedlund
  • 128,221
  • 31
  • 203
  • 222
  • This doesn't explain why preventing the default behavior of a parent element prevents children behaviors. The OP clearly understands what `preventDefault` does. – Ian Apr 02 '13 at 14:49
  • I thought it was pretty clear. Added a bit of clarification; don't know if you'll think it makes any difference, tho. – David Hedlund Apr 02 '13 at 14:53
  • @Ian it seemed pretty clear to me - saw you copied & pasted your comment from another answer, did you read this answer completely before commenting? – Adrian Apr 02 '13 at 14:54
  • @Adrian All this answer (and the other answer) does is explain what `preventDefault` and `stopPropagation` do. The OP has no problem understanding their purpose. The question is in regards to a parent/ancestor element preventing the default behavior of an event which was triggered by a child/descendent element. The point is that the `event` object that is passed between these handlers is the same, so when a parent prevents the default behavior of an event, it doesn't matter where it originated - it will prevent its default behavior. – Ian Apr 02 '13 at 15:00
  • 1
    What @Ian said; how do you explain the fact that the checkbox cannot be checked but the radio button can? http://jsfiddle.net/R7dgW/ – Explosion Pills Apr 02 '13 at 15:03
  • @DavidHedlund And that's why I'm upvoting, because I think that's exactly what the OP needs. I just wanted to explain more of what I meant in the first place – Ian Apr 02 '13 at 15:03
  • I think an important thing to note is the `e.target`. Since the `event` bubbles up, you will see events pass through the `
    ` that were triggered by the `` (or any other descendents) and will be the target. Since it bubbled and passes through the `
    `, calling `preventDefault` on it isn't anything special compared to a handler where it originated. It will still prevent the default behavior of that specific event.
    – Ian Apr 02 '13 at 15:05
  • @ExplosionPills The radio cannot be checked on my Firefox. – Fabrício Matté Apr 02 '13 at 15:06
  • @FabrícioMatté I think they meant something more like http://jsfiddle.net/R7dgW/1/ - since the event wasn't propagated, the parent listener wasn't able to prevent the default behavior – Ian Apr 02 '13 at 15:10
  • @Ian that's not what I meant, but he is right; FFX does not select the radio button, but Chrome does. This indicates to me that this is not necessarily intended. – Explosion Pills Apr 02 '13 at 15:15
  • @ExplosionPills Ahh okay. That's interesting...even if I change it to be jQuery events, the same thing happens in FF/Chrome. http://jsfiddle.net/R7dgW/2/ - I thought maybe that would make things more consistent/"correct" – Ian Apr 02 '13 at 15:19
  • @Ian I think that Chrome does not use `click` as the activation event for radio buttons, which is weird because I think that's what it *should* be, but if there is another event that does trigger it I can't find it – Explosion Pills Apr 02 '13 at 15:33
  • 2
    Nice concise answer. The precise purposes of `preventDefault` and `stopPropagation` needed clarifying in my head - I had always looked at them both crudely as "things to try when you run into ancestor/descendent problems". Many thanks! – peteorpeter Apr 02 '13 at 17:13