This is a standard (unfortunate) browser behavior.
Attribute for
assigns <label>
to <input>
, so when <label>
element is clicked, browser will emulate a click on <input>
element right after your real click.
On a plus side, this allows focus of <input type="text"
, switching of <input type="radio"
, or toggling of <input type="checkbox"
.
But for the unfortunate side, this also causes that both elements send the click event. In case you are listening on clicks on a parent element, this means that you'll receive one "human interaction" twice. Once from <input>
and once from "the clicked element".
For those who wonder, <input>
could be inside <label>
element, you'll get less styling possibility, but you can then click in between check-box and text.
Your putting <span>
and <input>
inside <label>
actually creates a nice test case.
Try clicking from left to right;
On the text, you'll receive SPAN + INPUT events,
between text and check-box you'll get LABEL + INPUT events,
on the check-box directly only INPUT event,
then further right, only DIV event.
(because DIV is a block element and spans all the way to the right)
One solution would be to listen only on <input>
element events, but then you will not capture <div>
clicks and you also can't put <div>
inside <label>
.
The simplest thing to do is to ignore clicks on <label>
and all clickable elements inside <label>
except <input>
. In this case <span>
.
const elWrapper = document.querySelector('.wrapper');
elWrapper.addEventListener('click', handleLabelClick);
function handleLabelClick(event) {
console.log('Event received from tagName: '+event.target.tagName);
if (event.target.tagName === 'LABEL' || event.target.tagName === 'SPAN') { return; }
console.log('Performing some action only once. Triggered by click on: '+event.target.tagName);
}
<div class="wrapper">
<label for="option1">
<span>Select me</span>
<input id="option1" type="checkbox">
</label>
</div>
(from OP's example I changed parent to wrapper and label to elWrapper,
because parent and label are keywords.)
The solution from @T.J. causes events from <label>
and everything inside it to be ignored down-the-road.
To get the event fired, you'd need to click somewhere on the <div>
, but not directly on the text or check-box.
I added my answer because I didn't think this was the OP's intention.
But even for this other case, you might use similar approach as I offered above. Checking if clicked element name is DIV then allowing further actions. I think it's more straightforward, more localized (doesn't affect event propagation), more universal (if you select capturing: addEventListener(...,...,true) this will still work)