Why is my click event called twice in jquery?
Asked Answered
S

5

18

Why is my click event fired twice in jquery?

HTML

<ul class=submenu>
    <li><label for=toggle><input id=toggle type=checkbox checked>Show</label></li>
</ul>

Javascript

$("ul.submenu li:contains('Show')").on("click", function(e) {
    console.log("toggle");
    if ($(this).find("[type=checkbox]").is(":checked")) console.log("Show");
    else console.log("Hide");
});

This is what I get in console:

toggle                     menu.js:39
Show                       menu.js:40
toggle                     menu.js:39
Hide                       menu.js:41


> $("ul.submenu li:contains('Show')")
[<li>​                                            ]
    <label for=​"toggle">​
      <input id=​"toggle" type=​"checkbox" checked>​
      "Show"
    </label>​
</li>​
Saundra answered 25/10, 2013 at 16:32 Comment(3)
Only fires once when I try it here jsfiddle.net/LRjUcSubjacent
@DominicGreen: It depends on whether you click the label or the checkbox (or the li). If you click the label, then at least on Chrome, you get both events.Rebba
Yes, it's a little difficult to see but if you select the last text on the console, you can see the event fired twice after that.Saundra
R
46

If I remember correctly, I've seen this behavior on at least some browsers, where clicking the label both triggers a click on the label and on the input.

So if you ignore the events where e.target.tagName is "LABEL", you'll just get the one event. At least, that's what I get in my tests:

Rebba answered 25/10, 2013 at 16:36 Comment(6)
yes, you're right. I can also fire the event only for the checkbox like $("ul.submenu li:contains('Show borders') [type=checkbox]").on("click",callback); , thank you very much for your answer.Saundra
@shuji: Ah, okay, I thought you were hooking it on the li for a specific reason. :-)Rebba
Not really, it's just that this is the first time i use checkbox for my menus, so I was used to bind directly to li.Saundra
Could you use the change event here instead? that might fix your problem as well: jsbin.com/iRozUd/3Carbide
@Andrew Whitaker that added to the checkbox bind has the exact same result but I think it would be more proper for a checkbox.Saundra
This just saved me a few hours of hair pulling. Thanks, mate.Mastic
L
9

I recommend you use the change event on the input[type="checkbox"] which will only be triggered once. So as a solution to the above problem you might do the following:

$("#toggle").on("change", function(e) {
  if ($(this).is(":checked"))
    console.log("toggle: Show");
  else
    console.log("toggle: Hide");
});

https://jsfiddle.net/ssrboq3w/

The vanilla JS version using querySelector which isn't compatible with older versions of IE:

document.querySelector('#toggle').addEventListener('change',function(){
  if(this.checked)
    console.log('toggle: Show');
  else
    console.log('toggle: Hide');
});

https://jsfiddle.net/rp6vsyh6/

Lasting answered 24/11, 2017 at 12:23 Comment(1)
Should be the accepted answer as it sounds much more logicalMagdamagdaia
F
5

This behavior occurs when the input tag is structured within the label tag:

<label for="toggle"><input id="toggle" type="checkbox" checked>Show</label>

If the input checkbox is placed outside label, with the use of the id and for attributes, the multiple firing of the click event will not occur:

<label for="toggle">Show</label>
<input id="toggle" type="checkbox" checked>
Fia answered 26/7, 2015 at 1:3 Comment(1)
unfortunately, even after separation, the issue is happening.Impetuous
C
1

Not sure why this wasn't mentioned. But if:

  1. You don't want to move the input outside of the label (possibly because you don't want to alter the HTML).
  2. Checking by e.target.tagName or even e.target doesn't work for you because you have other elements inside the label (in my case it had spans holding an SVG with a path so e.target.tagName sometimes showed SVG and other times it showed PATH).
  3. You want the click handler to stay on the li (possibly because you have other items in the li besides the checkbox).

Then this should do the trick nicely.

$('label').on('click', function(e) {
  e.stopPropagation();
});
$('#toggle').on('click', function(e) {
  e.stopPropagation();
  $(this).closest('li').trigger('click');
});

Then you can write your own li click handler without worrying about events being triggered twice. Personally, I prefer to use a data-selected attribute that changes from false to true and vice versa each time the li is clicked instead of relying on the input's value:

$('ul.submenu li').on('click', function() {
  let _li = $(this),
      ticked = _li.attr('data-selected');
  
  ticked = (ticked === 'false') ? true : false;
  _li.attr('data-selected', ticked);
  _li.find('#toggle').prop('checked', ticked);
});
Creigh answered 30/9, 2022 at 6:31 Comment(0)
V
0

I found that when I had the click (or change) event defined in a location in the code that was called multiple times, this issue occurred. Move definition to click event to document ready and you should be all set.

Vosges answered 25/3, 2021 at 12:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.