34

I have an div container which contains html checkbox and its label. Using jquery i wanted to trigger an click event when someone clicks on label in this container.

I see that jquery click event triggers twice when i click on label!

For testing purpose i triggered click event on checkbox instead of label and here checkbox click event triggers only once as it should be.

Here is the fiddle. http://jsfiddle.net/AnNvJ/2/

<tr>
    <td>
        <div id="test">
            <label>
                <input type="checkbox" id="1" />demo1</label>
            <label>
                <input type="checkbox" id="2" />demo2</label>
            <label>
                <input type="checkbox" id="3" />demo3</label>
        </div>
    </td>
    <td>&nbsp;</td>
    <td>&nbsp;</td>
</tr>

JQUERY

$(document).ready(function () {

    $('#test label').live('click', function (event) {
        alert('clicked');
    });


    $('#test checkbox').live('click', function (event) {
        alert('clicked');
    });

});
sravis
  • 3,562
  • 6
  • 36
  • 73
  • 4
    That's because the checkbox is the proper element to attach events to, not the label, and the `change` event would normally be the proper event to use, not `click`. – adeneo Jun 19 '13 at 07:30
  • 1
    Because your input checkbox is inside the label. – yeyene Jun 19 '13 at 07:31
  • Thank you everyone for anwering, I removed input from inside the label and it solved the issue but it wont toggle the check when i click on label. I figured out that by using for attribute in label its possible to toggle the check like – sravis Jun 19 '13 at 08:14

8 Answers8

45

The one of $('#test checkbox') is never called, because you don't have a tag with name checkbox.

And depending if you click on checkbox or the label, the callback for $('#test label') is called once or twice cause of the bubbling because the input element is part of the label and is one union and therefore also received the event if the label is click (it is not bubbling, you can't do event.stopPropagation()).

You can check this if you change your code this way:

 $('#test label').live('click', function (event) {
        event.stopPropagation(); //<-- has no effect to the described behavior
        console.log(event.target.tagName);
 });

Click on label:

LABEL
INPUT

Click on input:

INPUT

EDIT If you only want to handle the click on the label and not the checkbox - and you can't change your HTML structure - you can just ignore the event of the input element.

Either this way:

$('#test label').live('click', function (event) {
    if( event.target.tagName === "LABEL" ) {
         alert('clicked');
    }
});

Or using jQuery to test:

$('#test label').live('click', function (event) {
    if( $(event.target).is("label") ) {
         alert('clicked');
    }
});
t.niese
  • 39,256
  • 9
  • 74
  • 101
  • 3
    +1 for no bubbling here, but it's tricky because the union, he definitely need to split these – TecHunter Jun 19 '13 at 07:44
  • 1
    Splitted example : http://jsfiddle.net/techunter/Y8Mwf/ and you could follow this http://stackoverflow.com/questions/2728252/jquery-stoppropagation-bubble-down – TecHunter Jun 19 '13 at 07:50
  • @sravis added a short sample how you could filter without changing your html code. – t.niese Jun 19 '13 at 08:13
  • @t.niese, check the code in my answer below, it avoids the double trigger AND the checbox keeps its behaviour. – Federico J. Jun 19 '13 at 08:18
  • It is bubbling that causes the click handler on the label to get called twice when the label is clicked. In that case the click event is triggered for the label. Since the label is associated with an input, the default processing of the click on the label generates another click event for the input, and if the input is a descendent of the label that second click bubbles up to the label. So of course adding `event.stopPropagation()` has no effect --- the event has already bubbled up from the input to the label. – John S Jun 12 '15 at 18:34
9

It's because you put the input inside the label so when you click the checkbox you also click every parents (that's called bubbling) EDIT, credit to @t.niese : in fact there is no bubbling here because the issue is when you click on label and it only bubbles up.

To prevent double click event but also check the checkbox you can use :

$(document).ready(function () {

    $('#test label').on('click', function (event) {
        event.preventDefault();
        var $check = $(':checkbox', this);
        $check.prop('checked', !$check.prop('checked'));
        alert('clicked label');
    });


    $('#test :checkbox').on('click', function (event) {
        event.stopPropagation();
        alert('clicked checkbox');
    });
});

FIDDLE

Also prefer the usage of $.on and note the usage of :checkbox selector or [type="checkbox"] which according to JQuery API is faster and more compatible (attribute selectors)

event.preventDefault() will stop every action handled by browser for the native tag event.stopPropagation() prevents bubbling and any parent handlers from being notified of the event

TecHunter
  • 6,091
  • 2
  • 30
  • 47
  • If you are suggested to use .on instead of .live you must provide equivalent code like `$('#test').on('click', 'label', function (event) {` – z1m.in Jun 19 '13 at 07:39
  • Hi, for my own interest I checked what you posted, http://jsfiddle.net/uUZyn/ . Clicking only the ckecbox works like a charm, but when clicking on the label also checkbox alert is triggered, is this behaviour intended? – Federico J. Jun 19 '13 at 07:44
  • @Chococroc yeah checkout t.niese post up there he explains why. – TecHunter Jun 19 '13 at 07:49
  • @TecHunter Yes, like t.niese said, event.stopPropagation(); is not working. But as you told i removed input field out of label and its working fine now. But here if user clicks on label it wont select the checkbox. Whats the solution for this? – sravis Jun 19 '13 at 08:03
  • @sravis there you go, you can keep the input inside the label with this thus keeping some logical hierarchy – TecHunter Jun 19 '13 at 08:07
3

The OP found one solution to prevent the click event handler for the label from getting executed twice for a single click. The selected answer also has a good solution, but it incorrectly states that this has nothing to do with event bubbling.

The reason there are two clicks does have to do with event bubbling.

Clicking on a label that is associated with an input causes two click events to be triggered. The first click event is triggered for the label. The default handling of that click event causes a second click event to get triggered for the associated input. If the input is a descendent of the label, the second click event bubbles up to the label. That is why the click event handler for the label is called twice.


Therefore one way to solve this problem (as the OP discovered) is to not place the input inside the label element. There will still be two click events, but the second click event will not bubble up from the input to the label.

When the input is not inside the label, the label's "for" attribute can be used to associate the label with the input. The "for" attribute must be set to the "id" value of the input.

<input type="checkbox" id="1" />
<label for="1">demo1</label>

Another solution would be to stop the event from bubbling up from the input:

$('#test :checkbox').on('click', function(event) {
    event.stopPropagation();
});

$('#test label').on('click', function() {
    alert('clicked');
});

Then there's the solution presented in the selected answer, which is to have the label's click handler ignore the event when it bubbles up from the input. I prefer to write it like this though:

$('#test label').on('click', function(event) {
    if (event.target == this) {
        alert('clicked');
    }
});
John S
  • 21,212
  • 8
  • 46
  • 56
1

You MUST use preventDefault in your label and stopPropagation in your checkbox:

http://jsfiddle.net/uUZyn/5/

The Code:

$(document).ready(function () {

    $('#test label').on('click', function (event) {
      event.preventDefault();        
      alert('clicked label');
      var ele = $(this).find('input');
    if(ele.is(':checked')){
      ele.prop('checked', false);        
    }else{
      ele.prop('checked', true);
    }
  });

  $('#test :checkbox').on('click', function (event) {
    event.stopPropagation();
    alert('clicked checkbox');
  });
});

In this way you avoid the behaviour @t.niese explains

Thank you to the previous answers, I wouldn't figure out without them, :D.

Update

As t.niese points up, here is the answer I've updated with the behaviour:

http://jsfiddle.net/uUZyn/6/

Just added the check behaviour after using the preventDefault XD

Community
  • 1
  • 1
Federico J.
  • 15,388
  • 6
  • 32
  • 51
  • 2
    Sorry deleted the comment instead of editing :D if you do it this way the checkbox is not toggled when you click on the label, but this is the proposed behaviour. – t.niese Jun 19 '13 at 08:01
  • 1
    I figured out that by using for attribute in label its possible to toggle the check like – sravis Jun 19 '13 at 08:15
  • @Chococroc works ... but a little bit complicate ;) Why adding that much code if a short check if the target is `label` would be enough :D – t.niese Jun 19 '13 at 08:16
  • Oh yeah, when you have a hammer, all looks like a nail, hahaha, :D – Federico J. Jun 19 '13 at 08:20
  • @t.niese, good point, but after checking, (http://jsfiddle.net/uUZyn/8/) if you don't do all the workaround it doesn't work, may you check it? Maybe I'm wrong about the way of adding tags but not sure... – Federico J. Jun 19 '13 at 08:25
  • @Chococroc the `input` element is still inside the label. Using `for` you can move the input out of the `label`. The `for` does not change the behaviour but adds a connection without the requirement of nesting. – t.niese Jun 19 '13 at 08:28
  • Yeah, I was focused in the html and didn't realize of the nesting, :P. Thank you! – Federico J. Jun 19 '13 at 08:37
0

I just ran into this same issue where clicking on the label was triggering two separate click events. I was able to solve the problem for myself by adding a name attribute to the input and a for attribute to the label.

For example:

<div id="test">
    <label for="checkbox1">
        <input type="checkbox" name="checkbox1" id="1" />demo1</label>
    <label for="checkbox2">
        <input type="checkbox" name="checkbox2" id="2" />demo2</label>
    <label for="checkbox3">
        <input type="checkbox" name="checkbox3" id="3" />demo3</label>
 </div>

No JavaScript needed.

Will Hitchcock
  • 4,648
  • 3
  • 22
  • 32
  • 1
    The value of the `for` attribute is supposed to be an `id` value, not a `name` value. What you've done is you've disassociated the ` – John S Jun 12 '15 at 04:04
0

This happens mostly when you trigger 'click' for X element having child element Y and when user clicks on Y.

The event bubbled from actually clicked element is undesired event that you don't want.

You have two options to avoid this double triggering...

1) Use $('selector').triggerHandler('any_standard_event_name') - Here by standard event I mean 'click', 'change', 'hover' etc. (I have never tried this option)

2) Use custom event name. eg. $('selector').trigger('my.click');

Read more on http://api.jquery.com/trigger/

EGL 2-101
  • 1,203
  • 8
  • 17
0

Regarding my case and the fact that I'm using material design's md-button the suggestions above didn't work very well because it dramatically reduced the footprint of the area receiving finger press on my android. Not to mention instead of just 2 tagNames I would get upwards of 3 sometimes (BUTTON, SPAN, or MD-ICON). Also the suggestion elsewhere on the web to change my version of Aria didn't help either. I ended up finding the problem was that I was loading jquery before angular.js even though ng-infinate-scroll's help doc says to load jquery first. Anyway, if I tried to load jquery after angular ng-infinate-scroll would quit working. So I downloaded the latest version of ng-infinate-scroll (non-stable) version 1.2.0 and now I can load my jquery after angular.js and my infinite scroll works all the issues I had with ng-click firing multiple times is gone.

Post Impatica
  • 14,999
  • 9
  • 67
  • 78
0

My elements wrapped one another, also.

So i used a simple CSS trick for this solution :

.off{
   pointer-events:none;
}