2

I have some elements (for example divs with class .label) with radio buttons inside of each. When user clicks this "labels" I programmatically set radio in it as selected. But if I use preventDefault() for click event, the radio didn't selected if user clicked exactly on radio.

Please help me to understand this strange behaviour. I know the solution, I know why preventDefault() on parent element disallows to check radio, but I want to understand, why click event on radio can disallow to set its state programmatically. You will see that click on radio button will say that radio is checked, but it's not.

$(function () {
  $('.label').on('click', function(e) {
    var $radio = $(this).find(':radio')
    
    console.log('before prevent', `checked=${$radio.prop('checked')}`, `prevented=${e.isDefaultPrevented()}`);
    
    e.preventDefault();
    if (!$(this).hasClass('checked')) {
      $('.checked').removeClass('checked');
      $(this).addClass('checked');
    }
    $radio.prop('checked', true);
    
    console.log('after prevent', `checked=${$radio.prop('checked')}`, `prevented=${e.isDefaultPrevented()}`);
    
    setTimeout(function () {
      console.log('after timeout', `checked=${$radio.prop('checked')}`);
    }, 500);
  });
  $(':radio').on('click', function (e) {
    console.log('click', `prevented=${e.isDefaultPrevented()}`);
  });
});
.label {
  padding: 10px;
  margin-bottom: 10px;
  border: 1px solid #000;
}
.label.checked {
  background-color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="label">
  Label 1 <input type="radio" name="radio" value="1">
</div>
<div class="label">
  Label 2 <input type="radio" name="radio" value="2">
</div>

UPDATE. How do I see this situation:

  1. User clicks on radio
  2. Firstly event triggered on radio and input setted as checked.
  3. Then event is bubbling up and triggered on .label
  4. Calling preventDefault() sets up an internal cancelled flag.
  5. div getting class '.checked' and radio setted as checked again, now programmatically.
  6. Event bubbles on, but nothing happens any more.
  7. Since the event was cancelled, the default action should not occur and the checkbox is reset to its previous state.

Am I right?

mrDinckleman
  • 98
  • 1
  • 11
  • 1
    Is there any reason you need to use `e.preventDefault()` anyway? A `
    ` is not a clickable element by default, so there is no action/operation associated with a native click event on it anyway.
    – Terry Apr 13 '19 at 21:31
  • 1
    Btw, use semantics. A label should be a `label`. – Quentin Veron Apr 13 '19 at 21:32
  • @Terry I can remove e.preventDefault(), but when I met this situation I decided to find out, why I can't check radio programmatically if click event on it was set as preventDefault() – mrDinckleman Apr 13 '19 at 21:52
  • @QuentinVeron I simplified this code, in fact the structure is more complicated – mrDinckleman Apr 13 '19 at 21:54
  • Possible duplicate of [Why does preventDefault() on a parent element's click 'disable' a checkbox?](https://stackoverflow.com/questions/15767083/why-does-preventdefault-on-a-parent-elements-click-disable-a-checkbox) – Terry Apr 14 '19 at 08:16
  • @Terry I understand why preventDefault() disables input, I didn't understand why I can check it programmatically – mrDinckleman Apr 14 '19 at 17:57

3 Answers3

2

How do I see this situation (inspired by https://stackoverflow.com/a/15767580/11357125):

  1. You click on the radio
  2. It gets checked
  3. The event is dispatched on the document root
  4. 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. Event listener calls the preventDefault method, setting an internal cancelled flag. <div> getting class '.checked' and radio setted as checked again, now programmatically.
  8. Event 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 even after it was checked programmatically.
mrDinckleman
  • 98
  • 1
  • 11
1

Using preventDefault() on parent elements prevent the original event to be fired, but does not stop propagation. Understanding hierarchy and event propagation is crucial.

You have part of the solution in your code snippet. If you comment out that particular line, the code works properly, like you would expect. But if you use

e.stopPropagation();

it also works.

In order not to repeat information already on the web, I found a very similar case here (Why does preventDefault() on a parent element's click 'disable' a checkbox?) which may help you understand better event propagation and bubbling.

You can also find an even better explanation here (https://medium.freecodecamp.org/a-simplified-explanation-of-event-propagation-in-javascript-f9de7961a06e).

MDN documentation also rarely fails to impress (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault).

0

You just need to add e.stopPropagation() to return the default functionality for radio button again which is checked

The behaviour is that You have radio is a child to the div and you click listener is based on the parent div and when you preventDefault then the children inherit the prevention as well.

check that

If the button is pressed on one element and released on a different one, the event is fired on the most specific ancestor element that contained both.

mooga
  • 3,136
  • 4
  • 23
  • 38
  • Still doesnot work. Clicking the radio button doesnot checks it? – Maheer Ali Apr 13 '19 at 21:44
  • I think, you had to add additional click handler to radios with stopPropagation() inside and clicking label programmatically. But, as I said in my question, I know the solution, I want to understand behaviour. – mrDinckleman Apr 13 '19 at 22:02