jQuery Click dispara duas vezes ao clicar no rótulo

114

Estou usando o jQuery para criar botões de opção personalizados e estou com um problema. Ao clicar no rótulo associado ao rádio, os eventos de clique disparam duas vezes, se eu clicar apenas no próprio rádio, ele está funcionando bem (bem, na verdade não é o rádio que estou clicando, mas o div que envolve toda a entrada e o rótulo). Aqui está o código:

O 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>

Existe alguma solução para esse comportamento?

tom
fonte

Respostas:

130

Experimente adicionar:

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

ao .bind () ou .click (), o que você estiver vendo. Além disso, adicione o parâmetro evtà função, comofunction(evt) {...

Jordânia
fonte
9
Por que isso acontece?
chovy
8
Porque existem itens aninhados. Cada item na hierarquia irá borbulhar o evento.
Jordan
7
Se você estiver usando isso como uma caixa de seleção, também foi necessário para eu permitir que a entrada fosse realmente verificada:jQuery('input').prop('checked', true);
David Sinclair
6
return false;é equivalente a `evt.stopPropagation (); evt.preventDefault (); ( em jQuery )
manjericão
193

Tentei adicionar a solução acima adicionando:

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

mas não funcionou. No entanto, adicionando este:

evt.stopImmediatePropagation();

resolveu o problema! :)

N3da
fonte
6
Não sei por que, mas o que você disse parece funcionar para meu código. Obrigado!
shaosh,
8
Perfeito. Isso funcionou bem! Embora evt.stopPropagation(); evt.preventDefault();didn; 't
James111
1
só funcionou para mim, tentei as outras funções sem sucesso
gardarvalur
1
Essa resposta salvou minha vida! Obrigado: D
Bluetree
1
Essa foi a resposta para mim!
Dyluck de
73

Vincule o evento de clique à entrada em vez do rótulo. Quando o rótulo é clicado - o evento ainda ocorrerá porque, como Dustin mencionou, um clique no rótulo aciona um clique na entrada. Isso permitirá que o rótulo mantenha sua funcionalidade normal.

$('input').click();

Ao invés de

$('label').click();
dougmacknz
fonte
10
Essa solução também funciona se sua marcação usar a técnica de embrulhar o rótulo e apenas salvar minha sanidade: o)
Whelkaholism
4
você deve vincular até changemesmo ao botão de opção, pois o texto de um rótulo é clicável - eles nem sempre clicam no próprio botão de opção.
chovy
2
Se estiver usando uma label > inputconfiguração Bootstrapesque , esta é A resposta, não uma resposta. Adicionar evt.stopPropagation()ou evt.preventDefault()ao seu código, embora eficaz, é um hack que deve ser evitado quando a solução adequada é muito mais limpa e eficiente.
elPastor
uau uau apenas uau, passei quase dois dias tentando descobrir, finalmente isso ajudou e muito obrigado pela explicação
desenvolvedor de código-fonte aberto
isso salvou meu dia!
gab06 de
11

Se estiver tentando usar um contêiner externo como um elemento de clique, você também pode deixar os eventos borbulharem naturalmente e testar o elemento esperado em seu manipulador de cliques. Este cenário é útil se você estiver tentando definir o estilo de uma zona de clique exclusiva para um formulário.

<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
fonte
1
Isso funcionou para mim, pois eu ainda queria que o botão de opção fosse selecionado. Só não queria que o manipulador de eventos fizesse tudo duas vezes.
chovy
1
Isso não permitirá que crianças sejam selecionadas. A propósito, a melhor opção para obter a mesma funcionalidade seriaif (e.target == e.currentTarget) {}
Canja de Galinha
9

Para corrigir isso da maneira mais fácil, remova o atributo "for" do rótulo. Um clique no rótulo também acionará um clique no elemento associado. (que no seu caso está disparando o evento de clique duas vezes.)

Boa sorte

Dustin
fonte
Na verdade, a solução rápida. Pode-se argumentar que isso está bagunçando a marcação, mas eu diria que o propósito do atributo "for" é a propagação de eventos. Portanto, se você estiver usando outro mecanismo (jquery), elimine o "for".
Chris Harrington,
5

Eu costumo usar esta sintaxe

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

ao invés de

.click(function () { console.log('click'); })
Dalibor
fonte
5

A melhor resposta está escondida nos comentários:

você deve vincular até changemesmo ao botão de opção, pois o texto de um rótulo é clicável - eles nem sempre clicam no próprio botão de opção. - chovy 12 de dezembro de 13 às 1:45

Este violino ilustra que todas as outras soluções - stopPropagation, stopImmediatePropagation, preventDefault,return false - ou nada muda ou destruir a funcionalidade caixa / radio). Também ilustra que este é um problema de JavaScript básico, não um problema de jQuery.

EDIT: Outra solução de trabalho que acabei de encontrar em outro segmento é vincular o onclickà entrada em vez do rótulo. Violino atualizado .

WoodrowShigeru
fonte
2

Eu tentei adicionando solução.

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

mas não funcionou.

Adicionando

evt.stopImmediatePropagation();

resolveu o problema! :)

Janki Moradiya
fonte
1

O problema com e.preventDefault (); isso impede que o clique no rótulo verifique o botão de opção.

Uma solução melhor seria simplesmente adicionar uma verificação rápida "está marcada" como:

$("label").click(function(e){
  var rbtn = $(this).find("input");
  if(rbtn.is(':checked')){
  **All the code you want to have happen on click**
  }
)};
yoshyosh
fonte
1
Uma solução ainda mais sucinta seria apenas usar .mouseup em vez de .click
yoshyosh
1
Esta solução não funcionará se seu código também permitir que um usuário desmarque um botão de opção. O duplo incêndio causa sérios problemas neste caso.
Johncl
1

Meu problema é um pouco diferente, pois evt.stopPropagation();evt.preventDefault();não funciona pra mim, eu só adiciono return false;no final, aí funciona.

$("#addressDiv").on("click", ".goEditAddress", function(event) {
    alert("halo");
    return false;
});
GMsoF
fonte
1

No meu caso, o problema era que eu tinha o evento click em uma função e a função era executada duas vezes .... cada execução da função cria um novo evento click. - facepalm -

após mover o evento click para fora da função, tudo funcionou conforme o esperado! :)

FalcoB
fonte
0

Tente colocar sua tag de entrada fora do elemento acionador, porque a tag de rótulo emula o clique, então você sempre terá mais de uma chamada.

Benjamin
fonte
0

Eu tive o mesmo problema porque aninhei meu rádio dentro do rótulo como este com o manipulador anexado ao radio_div. Remover o rótulo aninhado corrigiu o problema.

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

O rótulo aciona o rádio / caixa de seleção a ser marcada.

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

Evita principalmente que o rótulo acione esse comportamento.

Kevin.B
fonte
0

O clique no rótulo com um atributo for = "some-id" aciona um novo clique, mas apenas se o destino existir e for uma entrada. I foi não capaz de resolvê-lo perfeitamente com e.preventDefault () ou coisas assim, então eu fiz assim:

Por exemplo, se você tem essa estrutura e deseja um evento clicando em .alguma classe

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

O que funcionou foi:

$(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
fonte