130

I am using jQuery to create custom radio buttons and i have a problem. When clicking on the label that associated with the radio the click events fires twice, if i click only on the radio itself it's working fine (well actually it's not the radio i am clicking but the div that wraps the whole input and label). Here is the code:

The HTML:

 <div id="box">
     <asp:RadioButtonList ID="RadioButtonList1" runat="server">
         <asp:ListItem>RADIO1</asp:ListItem>
         <asp:ListItem>RADIO2</asp:ListItem>
         <asp:ListItem>RADIO3</asp:ListItem>
     </asp:RadioButtonList>
</div>

jQuery:

<script type="text/javascript">
       $(function () {
            $('#box').find('input:radio').each(function (i) {

            var input = $(this);
            // get the associated label using the input's id
            var label = $('label[for=' + input.attr('id') + ']');
            // wrap the input + label in a div
            $('<div class="custom-radio"></div>').insertBefore(input).append(label, input);

            var wrapperDiv = input.parent();

            // find all inputs in this set using the shared name attribute
            var allInputs = $('input[name=' + input.attr('name') + ']');

            // necessary for browsers that don't support the :hover pseudo class on labels
            label.hover(

            function () {
                $(this).addClass('hover');
            }, function () {
                $(this).removeClass('hover checkedHover');
            });

            //bind custom event, trigger it, bind click,focus,blur events
            wrapperDiv.bind('updateState', function () {
                if ($(this)[0].children[1].checked) {
                    allInputs.each(function () {
                        var curDiv = $('div > label[for=' + $(this).attr('id') + ']').parent();
                        curDiv.removeClass('custom-radio-checked');
                        curDiv.addClass('custom-radio');
                    });
                    $(this).toggleClass('custom-radio custom-radio-checked');
                }
                else {
                    $(this).removeClass('custom-radio-checked checkedHover checkedFocus');
                }

            })
            .trigger('updateState')
            .click(function () { console.log('click'); })
            .focus(function () {
                label.addClass('focus');
            }).blur(function () {
                label.removeClass('focus checkedFocus');
            });
        }); 
       });
   </script>

Is there any solution for this behaviour?

tone
  • 1,535
  • 3
  • 15
  • 20

17 Answers17

228

I tried adding the solution above by adding:

evt.stopPropagation();
evt.preventDefault();

but didn't work. However adding this:

evt.stopImmediatePropagation();

solved the problem! :)

N3da
  • 4,323
  • 5
  • 20
  • 22
142

Try adding:

evt.stopPropagation();
evt.preventDefault();

to the .bind() or .click(), whichever you're seeing. Also, add the parameter evt to the function, like function(evt) {...

Jordan
  • 31,971
  • 6
  • 56
  • 67
82

Bind the click event to the input rather than the label. When the label is clicked - the event will still occur because, as Dustin mentioned, a click on the label triggers a click on the input. This will allow the label to hold its normal functionality.

$('input').click();

Instead of

$('label').click();
dougmacknz
  • 828
  • 6
  • 6
  • 13
    This solution also works if your markup uses the label-wrapping-the-input technique, and just saved my sanity :o) – Whelkaholism Dec 21 '12 at 11:39
  • 4
    you should actually bind to `change` even on the radio button since the text of a label is clickable -- they don't always click the radio button itself. – chovy Dec 12 '13 at 01:45
  • 2
    If using a Bootstrapesque `label > input` setup, this is THE answer, not an answer. Adding `evt.stopPropagation()` or `evt.preventDefault()` to your code, while effective, is a hack that should be avoided when the proper solution is much cleaner and more efficient. – elPastor Jan 03 '18 at 00:47
11

If you're trying to use an outer container as a click element you can also let the events bubble naturally and test for the expected element in your click handler. This scenario is useful if you're trying to style a unique click zone for a form.

<form>
<div id="outer">
    <label for="mycheckbox">My Checkbox</label>
    <input type="checkbox" name="mycheckbox" id="mycheckbox" value="on"/>
</div>
</form>
<script>
$('#outer').on('click', function(e){
    // this fires for #outer, label, and input
    if (e.target.tagName == 'INPUT'){
        // only interested in input
        console.log(this);
    }
});
</script>
tobius
  • 825
  • 1
  • 8
  • 10
  • 1
    This worked for me as i still wanted the radio button to be selected. I just didn't want the event handler do to everything twice. – chovy Dec 14 '13 at 01:50
  • 1
    This won't allow children to be selected. BTW, a better option to achieve the same functionality would be `if (e.target == e.currentTarget) {}` – dǝɥɔS ʇoıןןƎ May 03 '18 at 14:21
9

To fix this the easy way, remove the "for" attribute on the label. A click on the label will also trigger a click on the associated element. (which in your case is firing your click event twice.)

Good luck

Dustin
  • 99
  • 1
  • 1
  • Indeed the quick solution. One might argue that it is messing with the markup, but I'd counter that the purpose of the "for" attribute is event propogation. So if you are using another mechanism (jquery) then by all means get rid of "for". – Chris Harrington Oct 07 '14 at 15:20
6

I usually use this synthax

.off('click').on('click', function () { console.log('click'); })

instead of

.click(function () { console.log('click'); })
Dalibor
  • 1,430
  • 18
  • 42
6

I have tried by adding solution.

evt.stopPropagation();
evt.preventDefault();

but didn't work.

By adding

evt.stopImmediatePropagation();

solved the problem! :)

5

Best answer is hidden inside comments:

you should actually bind to change even on the radio button since the text of a label is clickable -- they don't always click the radio button itself. – chovy Dec 12 '13 at 1:45

This fiddle illustrates that all the other solutions – stopPropagation, stopImmediatePropagation, preventDefault, return false – either change nothing or destroy the checkbox/radio functionality). It also illustrates that this is a vanilla JavaScript problem, not a jQuery problem.

EDIT: Another working solution that I just found in another thread is to bind the onclick to the input rather than the label. Updated fiddle.

WoodrowShigeru
  • 1,418
  • 1
  • 18
  • 25
1

The problem with e.preventDefault(); is it stops the label click from checking the radio button.

A better solution would be to simply add a "is checked" quick check like so:

$("label").click(function(e){
  var rbtn = $(this).find("input");
  if(rbtn.is(':checked')){
  **All the code you want to have happen on click**
  }
)};
yoshyosh
  • 13,956
  • 14
  • 38
  • 46
  • 1
    An even more succinct solution would be just using .mouseup rather than .click – yoshyosh Jan 05 '13 at 23:34
  • 1
    This solution does not work if your code is also going to allow a user to deselect a radio button. The double fire causes serious troubles in this case. – Johncl Feb 19 '14 at 08:02
  • @yoshyosh .mouseup would only work for mouse action. Remember checkboxes can also be checked with "space bar" key on the keyboard. – Wale Sep 18 '20 at 11:51
1

My problem is a bit different, as the evt.stopPropagation();evt.preventDefault(); doesn't work for me, I just add return false; in the end, then it works.

$("#addressDiv").on("click", ".goEditAddress", function(event) {
    alert("halo");
    return false;
});
Sam YC
  • 10,725
  • 19
  • 102
  • 158
1

In my case the problem was that i had the click event in a function and the function was executed twice.... every execution of the function creates a new click event. -facepalm-

after moving the click event outside the function, everything worked as expected! :)

FalcoB
  • 1,333
  • 10
  • 25
0

Try put your input tag outside trigger element, because label tag emulates click, so you will have always more than one call.

Benjamin
  • 558
  • 7
  • 15
0

I had the same issue because I had nested my radio inside the label like this with the handler attached to radio_div. Removing the nested label fixed the issue.

<div id="radio_div">
    <label>
       <input type="radio" class="community_radio" name="community_radio" value="existing">
           Add To Existing Community
    </label>
</div>
shaw2thefloor
  • 600
  • 5
  • 20
0

The label triggers the radio/checkbox to be checked.

if ($(event.target).is('label')){
    event.preventDefault();
}

It prevent especially the label to trigger this behavior.

Kevin.B
  • 81
  • 2
  • 5
0

The click on the label with a for="some-id" attribute triggers a new click, but only if the target exists and is an input. I was not able to solve it perfectly with e.preventDefault() or stuff like that so I did it like this:

For example, if you have this structure and want an event on clicking .some-class

<div class="some-class">
    <input type=checkbox" id="the-input-id" />
    <label for="the-input-id">Label</label>
</div>

What did work was:

$(document)
    .on('click', '.some-class', function(e) {
        if(
            $(e.target).is('label[for]')
            &&
            $('input#' + $(e.target).attr('for')).length
        ) {
            // This will trigger a new click so we are out of here
            return;
        }

        // else do your thing here, it will not get called twice
    })
;
Julesezaar
  • 2,658
  • 1
  • 21
  • 21
0

Yes, solved it by adding, https://www.w3schools.com/jsref/event_stopimmediatepropagation.asp

e.stopImmediatePropagation(); < - this stop multiple fires ⌚

$(document).on("click",".SaveGSTPayableAndRefund",function(e){
   e.stopImmediatePropagation();
})

Thanks :)

-2
$(document).on('click','.class',function() {
    alert('abc')
})

In my case it fired 2 times because I included this code 2 times like assets/js/test.js 2 times.

blackgreen
  • 34,072
  • 23
  • 111
  • 129