Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML Label doesn't trigger the respective input if the mouse gets moved while clicking in Firefox

In the following example, when you click on the label, the input changes state.

document.querySelector("label").addEventListener("click", function() {
  console.log("clicked label");
});
label {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
<input type="checkbox" id="1">
<label for="1">Label</label>

In Chrome, when you move the cursor between the mousedown and mouseup events the input still gets triggered, whereas in Firefox the checkbox doesn't change state.

Is there a way to fix this? (without using JavaScript event listeners)

Firefox version: 69.0.3 (64-bit)

Full set of actions when using chrome.

  1. Press the button over the label
  2. Move the cursor around (even outside the label) while still holding the button
  3. Return the cursor back to the label
  4. Release the button
like image 882
nick zoum Avatar asked Nov 13 '19 15:11

nick zoum


People also ask

How do I trigger a click from an input in labels?

Labels should have “for” attributes that match the ID of the input they are labeling. This means we can snag that attribute and use it in a selector to trigger a click on the input itself. Assuming of course you have some reason to watch for clicks on inputs.

Where does the label come before the input in HTML?

If we have some standard HTML where the label comes before the input: That places the label before the input in the DOM. But now let’s say our label and form are inside a flexible container and we use CSS order to reverse things where the input visually comes before the label:

What is a labeled control in HTML?

The form control that the label is labeling is called the labeled control of the label element. One input can be associated with multiple labels. When a <label> is clicked or tapped and it is associated with a form control, the resulting click event is also raised for the associated control.

Should the input focus before or after the label?

A screen reader user, who is navigating between elements, might expect the input to gain focus before the label because the input comes first visually. But what really happens is the label comes into focus instead. See here for the difference between navigating with a screen reader and a keyboard. So, we should be mindful of that.


3 Answers

No. This looks like a firefox bug and not an issue with your code. I don't believe there is a css workaround for this behavior.

You may be able to report it to Mozilla and get the issue fixed, but I wouldn't rely on that. https://bugzilla.mozilla.org/home

For a potential workaround I would suggested triggering the event on mouseup instead.

like image 147
Garet Webster Avatar answered Oct 27 '22 00:10

Garet Webster


Introduction

Although I specifically stated in the question that the answer shouldn't involve JavaScript, all the answers worked with JavaScript.
Since this seems to be a Firefox bug and most of the answers submitted at this point would require me to also alter the rest of my code, I decided to create a script that can be run once, will deal with all the labels regardless of when they are added to the dom and will have the least impact on my other scripts.

Solution - Example

var mutationConfiguration = {
  attributes: true,
  childList: true
};

if (document.readyState === "complete") onLoad();
else addEventListener("load", onLoad);

var managingDoms = [];

function onLoad() {
  document.querySelectorAll("label[for]").forEach(manageLabel);
  if (typeof MutationObserver === "function") {
    var observer = new MutationObserver(function(list) {
      list.forEach(function(item) {
        ({
          "attributes": function() {
            if (!(item.target instanceof HTMLLabelElement)) return;
            if (item.attributeName === "for") manageLabel(item.target);
          },
          "childList": function() {
            item.addedNodes.forEach(function(newNode) {
              if (!(newNode instanceof HTMLLabelElement)) return;
              if (newNode.hasAttribute("for")) manageLabel(newNode);
            });
          }
        }[item.type])();
      });
    });
    observer.observe(document.body, mutationConfiguration);
  }
}

function manageLabel(label) {
  if (managingDoms.includes(label)) return;
  label.addEventListener("click", onLabelClick);
  managingDoms.push(label);
}

function onLabelClick(event) {
  if (event.defaultPrevented) return;
  var id = this.getAttribute("for");
  var target = document.getElementById(id);
  if (target !== null) {
    this.removeAttribute("for");
    var self = this;
    target.click();
    target.focus();
    setTimeout(function() {
      self.setAttribute("for", id);
    }, 0);
  }
}
label {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  padding: 10px;
  border: 1px solid black;
  cursor: pointer;
}
<input type="checkbox" id="a">
<input type="text" id="b">
<label for="a">A</label>
<script>
  setTimeout(function() {
    var label = document.createElement("label");
    label.setAttribute("for", "b");
    label.textContent = "b";
    document.body.appendChild(label);
  }, 3E3);
</script>

Explanation

onLabelClick

The function onLabelClick needs to get called whenever a label gets clicked, it will check if the label has a corresponding input element. If it does, it will trigger it, remove the for attribute of the label so that the browsers don't have the bug won't re-trigger it and then use a setTimeout of 0ms to add the for attribute back once the event has bubbled up. This means event.preventDefault doesn't have to get called and thus no other actions/events will get cancelled. Also if I need to override this function I just have to add an event-listener that calls Event#preventDefault or removes the for attribute.

manageLabel

The function manageLabel accepts a label checks if it has already been added an event listener to avoid re-adding it, adds the listener if it hasn't already been added, and adds it to the list of labels have been managed.

onLoad

The function onLoad needs to get called when the page gets loaded so that the function manageLabel can be called for all the labels on the DOM at that moment. The function also uses a MutationObserver to catch any labels that get added, after the load has been fired (and the script has been run).

The code displayed above was optimized by Martin Barker.

like image 21
nick zoum Avatar answered Oct 26 '22 23:10

nick zoum


I know you did not want JS Event listeners, but im thinking you mean to identify the movement this does not but is using mousedown instead of click (mousedown followed by mouseup).

While this is a known bug in Firefox you could get around it by using the mousedown event

I have had to change your id to be a valid one id's must start with a character

document.querySelector("label").addEventListener("mousedown", function(evt) {
  console.log("clicked label");
  // if you want to to check the checkbox when it happens,
  let elmId = evt.target.getAttribute("for")
  let oldState = document.querySelector("#"+elmId).checked;
  setTimeout(() => {
    if(oldState == document.querySelector("#"+elmId).checked){
      document.querySelector("#"+elmId).checked = !oldState;
    }
  }, 150)
});
label {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
<input type="checkbox" id="valid_1">
<label for="valid_1">Label</label>
like image 29
Barkermn01 Avatar answered Oct 26 '22 23:10

Barkermn01