Search code examples
javascriptcheckboxpreventdefault

Why does preventDefault on parent element disable programmatically checking child checkbox on click?


A div element's click event has e.preventDefault() at the beginning. That makes manually clicking the checkbox input or it's associated label inside no longer works.

With the help of some more JavaScript code, manually clicking the label generates expected results because the checkbox is now programmatically checked/unchecked.

However, manually clicking the checkbox input still does not work despite the fact that similar JavaScript code for programmatically checking/unchecking has been implemented. Why?

document.querySelector('div').onclick = function (e)
{
      e.preventDefault();
      if (e.target.tagName.toUpperCase() == 'LABEL')
      {
          e.target.previousElementSibling.checked = !e.target.previousElementSibling.checked;
      }
      else if (e.target.tagName.toUpperCase() == 'INPUT')
      {
          e.target.checked = !e.target.checked;
      }
}
<div>
      <input  id="check" type="checkbox">
      <label for="check">Label Text</label>
</div>


Solution

  • Clicking the label sets the checked property as expected because there is no default action related to the label to be canceled. Clicking the input sets the property as expected, but due to the default action (toggling the checked property) being prevented it reverts to its previous state.

    see: Why does preventDefault on checkbox click event returns true for the checked attribute? for more in depth discussion.

    document.querySelector('div').onclick = function (e)
    {
          e.preventDefault();
          if (e.target.tagName.toUpperCase() == 'LABEL')
          {
              e.target.previousElementSibling.checked = !e.target.previousElementSibling.checked;
          }
          else if (e.target.tagName.toUpperCase() == 'INPUT')
          {
          console.clear();
          console.log(e.target.checked);
              e.target.checked = !e.target.checked;
          console.log(e.target.checked);
    }
    }
    <div>
          <input  id="check" type="checkbox">
          <label for="check">Label Text</label>
    </div>

    Edit

    In response to your comment I think the cleanest solution would be to explicitly apply listeners to those elements that you want to control outside of the provided API methods calling stopPropagation() on them to avoid triggering the parent listeners.

    You can then handle whatever logic you need without having to work around artifacts of the outside methods. If you need the parent to listener to run as well you can programmatically activate it after your control logic is finished.

    // original API listener
    document.querySelector('.container').addEventListener('click', function (e) {
      e.preventDefault();
      
      // add container specific code
      console.log('clicked div');
     
    });
    
    // your isolated listener
    document.querySelector('.checkbox-container').addEventListener('click', function (e) {
      e.stopPropagation(); // stop the event from propagating to the parent listener
      
      // add checkbox specific code 
      console.clear();
      console.log('clicked checkbox container');
      
      // even programmatically 'clicking' parent if you need to
      e.currentTarget.parentElement.click();
    });
    .container {
      width: 150px;
      height: 60px;
      background-color: lightgray;
    }
    
    .checkbox-container {
      display: inline-block;
      background-color: aquamarine;
    }
    <div class="container">
      <div class="checkbox-container">
        <input id="check" type="checkbox">
        <label for="check" >Label Text</label>
      </div>
    </div>