Search code examples
javascriptformslabel

Cannot toggle class to label in forms


I was working on a modal component in Angular and was trying to toggle a class state on a custom input checkbox on clicking on the label, but it didn't work, I thought it was something with material or else with Angular, but also tried with plain HTML and vanilla JS and it does not allow to change or toggle a class in the label. How could this be? Is there something I am missing with the label click behavior? If you click directly on the input it adds the class 'active' but if clicked the 'M' it doesn't. I am hiding the input from the view since I am styling it to look different.

Here is a piece of code for trying here:

window.addEventListener('load', function() {
  let elems = document.querySelectorAll('.form-check-label');
  elems.forEach((e) => e.addEventListener('click', function() {
    console.log(e.classList);
  }, false));
}, false);

function checkInput(elem) {
  let checkMark = elem.querySelector('input');
  checkMark.checked = !checkMark.checked;
}
.form-check-label {
  cursor: pointer;
}
<label 
onClick = "this.classList.toggle('active');"
class="form-check-label" 
for="Monday">
  <input 
  class="form-check-input" 
  type="checkbox" 
  id="Monday" 
  value="Monday" />
  <span class="day-week">
   M
  </span>
</label>

<br />
<br />

<div 
onClick = "this.classList.toggle('active');checkInput(this);"
class="form-check-label" 
>
  <input 
  class="form-check-input" 
  type="checkbox" 
  value="Monday" />
  <span class="day-week">
   M
  </span>
</div>

If I change the label for a div, then it works. You can check the class state change in the browser console.


Solution

  • This is a interesting case which seems to be related to the behaviour of input controlled by label. It can be observed that the click on label generate actually 2 click events, first coming from span and second (which seems to be a browser synthetic event nevertheless "real") from input, which sets the focus on input element, this is most obvious when using a text type input. So, the first click sets the class while the second removes it, that happens almost instantaneous.

    As a simple solution is to discard the click event generated by input with event.stopPropagation(), although it will disable a real click on checkbox:

    window.addEventListener('load', function() {
      let elems = document.querySelectorAll('.form-check-label');
      elems.forEach((e) => e.addEventListener('click', function() {
        console.log(e);
      }, false));
    }, false);
    
    function checkInput(elem) {
      let checkMark = elem.querySelector('input');
      checkMark.checked = !checkMark.checked;
    }
    .form-check-label {
      cursor: pointer;
    }
    <label 
    onClick = "this.classList.toggle('active');"
    class="form-check-label" 
    for="Monday">
      <input 
      onClick = "event.stopPropagation();"
      class="form-check-input" 
      type="checkbox" 
      id="Monday" 
      value="Monday" />
      <span class="day-week">
       M
      </span>
    </label>
    
    <br />
    <br />
    
    <div 
    onClick = "this.classList.toggle('active');checkInput(this);"
    class="form-check-label" 
    >
      <input 
      class="form-check-input" 
      type="checkbox" 
      value="Monday" />
      <span class="day-week">
       M
      </span>
    </div>