Comment vérifier si l'événement de clic est déjà lié - JQuery

130

Je lie un événement de clic avec un bouton:

$('#myButton').bind('click',  onButtonClicked);

Dans un scénario, cela est appelé plusieurs fois, donc lorsque je fais un, triggerje vois plusieurs appels ajax que je veux empêcher.

Comment puis-je bindseulement si ce n'est pas lié avant.

Poussiéreux
la source
1
Mec, je pense que + Konrad Garus a la réponse la plus sûre ( stackoverflow.com/a/6930078/842768 ), pensez à changer votre réponse acceptée. À votre santé! MISE À JOUR: Vérifiez également le commentaire d'Herbert Peters! C'est la meilleure approche.
giovannipds
Pour les normes actuelles, voir la réponse de @A Morel ci-dessous ( stackoverflow.com/a/50097988/1163705 ). Moins de codage et élimine tout le travail de conjecture, les algorithmes et / ou la recherche d'éléments existants de l'équation.
Xandor

Réponses:

117

Mise à jour du 24 août 2012 : dans jQuery 1.8, il n'est plus possible d'accéder aux événements de l'élément en utilisant .data('events'). (Voir ce bogue pour plus de détails.) Il est possible d'accéder aux mêmes données avec jQuery._data(elem, 'events'), une structure de données interne, qui n'est pas documentée et donc pas garantie à 100% de rester stable. Cela ne devrait cependant pas être un problème, et la ligne correspondante du code du plugin ci-dessus peut être modifiée comme suit:

var data = jQuery._data(this[0], 'events')[type];

Les événements jQuery sont stockés dans un objet de données appelé events, vous pouvez donc rechercher dans ceci:

var button = $('#myButton');
if (-1 !== $.inArray(onButtonClicked, button.data('events').click)) {
    button.click(onButtonClicked);
}

Il serait bien sûr préférable que vous puissiez structurer votre application pour que ce code ne soit appelé qu'une seule fois.


Cela pourrait être encapsulé dans un plugin:

$.fn.isBound = function(type, fn) {
    var data = this.data('events')[type];

    if (data === undefined || data.length === 0) {
        return false;
    }

    return (-1 !== $.inArray(fn, data));
};

Vous pourriez alors appeler:

var button = $('#myButton');
if (!button.isBound('click', onButtonClicked)) {
    button.click(onButtonClicked);
}
seul jour
la source
10
+1 pour la modification. Lire ce post aujourd'hui et j'utilise 1.8. THX.
Gigi2m02
2
Merci pour la modification. Est-ce uniquement pour les boutons ou pourrais-je également l'utiliser <a>? Parce que quand j'essaye, il dit jQuery._data()l'erreur suivanteTypeError: a is undefined
Houman
J'enroulerais la ligne var data = jQuery._data(this[0], 'events')[type];dans une capture d'essai et retournerais false dans la capture. Si aucun événement n'est lié à ce [0], un appel à [type] provoquera une variation de "Impossible d'obtenir la propriété 'clic' d'une référence non définie ou nulle" et évidemment cela vous indique également ce que vous devez savoir.
Robb Vandaveer
Je ne sais pas si l'objet _data a changé dans jQuery 2.0.3 mais je n'ai pas pu utiliser $ .inArray pour cela. La fonction que vous souhaitez comparer est dans une propriété de chaque élément de données appelé «gestionnaire». Je l'ai modifié pour utiliser une simple instruction for et j'ai vérifié l'équivalence des chaînes, ce que je suppose que inArray a fait. for (var i = 0; i < data.length; i++) { if (data[i].handler.toString() === fn.toString()) { return true; } }
Robb Vandaveer
pourquoi ne pas déplacer votre mise à jour / modification vers le haut? ... C'est la vraie bonne réponse.
Don Cheadle
180

Une autre façon - marquez ces boutons avec une classe CSS et un filtre:

$('#myButton:not(.bound)').addClass('bound').bind('click',  onButtonClicked);

Dans les versions récentes de jQuery, remplacez-le bindpar on:

$('#myButton:not(.bound)').addClass('bound').on('click',  onButtonClicked);
Konrad Garus
la source
4
Excellent! Merci Konrad, cela frappe le sweet spot. J'adore les approches élégantes et simples comme celle-ci. Je suis à peu près sûr qu'il est également plus performant, car chaque élément (auquel sont déjà associés des gestionnaires d'événements de clic) n'a pas besoin de faire une recherche complète dans certains grands ensembles d'événements en comparant chacun d'eux. Dans mon implémentation, j'ai nommé la classe d'assistance ajoutée "click-bound", ce qui, à mon avis, est une option plus facile à gérer: $ (". List-item: not (.click-bound)"). AddClass ("click-bound") ;
Nicholas Petersen
1
Comme indiqué dans la réponse acceptée: "Il serait bien sûr préférable que vous puissiez structurer votre application pour que ce code ne soit appelé qu'une seule fois." Cela accomplit cela.
Jeff Dege
+1 dans ma situation, off / on et bind / unind ont empêché plusieurs appels. C'était la solution qui fonctionnait.
Jay
2
C'est la meilleure solution pour les performances car le code peut vérifier la présence de la classe CSS et de la liaison de glissement déjà présente. Une autre solution exécute un processus de détachement puis de rebind avec (au moins fr4om ce que je peux dire) plus de frais généraux.
Phil Nicholas
1
2 numéros. (lors de son utilisation dans un plug-in pouvant être lié à plusieurs éléments) Ne fonctionnera pas lors de la liaison d'un événement de redimensionnement à l'objet window. Utiliser des données ('lié', 1) au lieu de addClass ('lié') est une autre approche qui fonctionne. Lors de la destruction de l'événement, il peut le faire alors que d'autres instances en dépendent. Il est donc conseillé de vérifier si d'autres instances sont toujours utilisées.
Herbert Peters
59

Si vous utilisez jQuery 1.7+:

Vous pouvez appeler offavant on:

$('#myButton').off('click', onButtonClicked) // remove handler
              .on('click', onButtonClicked); // add handler

Si non:

Vous pouvez simplement dissocier le premier événement:

$('#myButton').unbind('click', onButtonClicked) //remove handler
              .bind('click', onButtonClicked);  //add handler
Naftali alias Neal
la source
1
Ce n'est pas exactement une solution belle, mais il serait amélioré que par déliant celui gestionnaire: .unbind('click', onButtonClicked). Voir le manuel
lonesomeday
16
Vous pouvez en fait l'espace de noms de vos gestionnaires au fur et à mesure que vous les ajoutez, puis la dissociation devient assez simple. $(sel).unbind("click.myNamespace");
Marc
@lonesomeday est-ce que votre exemple d'utilisation de «délier» était destiné à montrer quelque chose de différent? N'est-ce pas .unbind effectivement identique à .off, vous devriez donc écrire$('#myButton').unbind('click', onButtonClicked).bind('click', onButtonClicked);
Jim
1
@Jim Vous verrez que la question a été modifiée trois ans après mon commentaire! (Et aussi dans les minutes qui suivent mon commentaire. Le contenu sur lequel je commentais n'existe plus, c'est pourquoi cela ne semble probablement pas avoir beaucoup de sens.)
lonesomeday
@lonesomeday hmmm Je ne sais pas pourquoi il a été modifié, pensez-vous que je devrais revenir en arrière?
Naftali aka Neal
8

La meilleure façon que je vois est d'utiliser live () ou delegate () pour capturer l'événement dans un parent et non dans chaque élément enfant.

Si votre bouton est à l'intérieur d'un élément #parent, vous pouvez remplacer:

$('#myButton').bind('click', onButtonClicked);

par

$('#parent').delegate('#myButton', 'click', onButtonClicked);

même si #myButton n'existe pas encore lorsque ce code est exécuté.

Pablonete
la source
2
Méthodes
obsolètes
8

J'ai écrit un tout petit plugin appelé "once" qui fait cela. Exécuter et rallumer dans l'élément.

$.fn.once = function(a, b) {
    return this.each(function() {
        $(this).off(a).on(a,b);
    });
};

Et simplement:

$(element).once('click', function(){
});
Rodolfo Jorge Nemer Nogueira
la source
9
.one () supprimera le gestionnaire après la première exécution.
Rodolfo Jorge Nemer Nogueira
3
ici "une fois" est pour attacher un gestionnaire une fois. "one" dans jquery est pour exécuter une fois sans attacher une fois.
Shishir Arora
1
Je sais que j'arrive après coup, mais ne offsupprime pas tous les gestionnaires pour le type donné? Cela signifie que s'il y avait un gestionnaire de clics distinct non lié mais sur le même élément, celui-ci ne serait-il pas également supprimé?
Xandor
utilisez simplement un espace de noms sur l'événement pour qu'il ne supprime pas tous les gestionnaires comme: 'keypup.test123'
SemanticZen
6

Pourquoi ne pas l'utiliser

unbind() avant bind()

$('#myButton').unbind().bind('click',  onButtonClicked);
Krillehbg
la source
1
$('#myButton').off().on('click', onButtonClicked);fonctionne aussi pour moi.
Veerakran Sereerungruangkul
Fonctionne pour moi ... solution gr8
imdadhusen
Belle. Fonctionne parfaitement pour moi pour un bouton qui était déjà lié.
seanbreeden
3

Voici ma version:

Utils.eventBoundToFunction = function (element, eventType, fCallback) {
    if (!element || !element.data('events') || !element.data('events')[eventType] || !fCallback) {
        return false;
    }

    for (runner in element.data('events')[eventType]) {
        if (element.data('events')[eventType][runner].handler == fCallback) {
            return true;
        }

    }

    return false;
};

Usage:

Utils.eventBoundToFunction(okButton, 'click', handleOkButtonFunction)
Yair Galler
la source
2

Basé sur la réponse @ konrad-garus, mais en utilisant data, car je pense qu'il classdevrait être utilisé principalement pour le style.

if (!el.data("bound")) {
  el.data("bound", true);
  el.on("event", function(e) { ... });
}
Ariel
la source
2

Pour éviter de vérifier / lier / délier, vous pouvez changer votre approche! Pourquoi n'utilisez-vous pas Jquery .on ()?

Depuis Jquery 1.7, .live (), .delegate () est obsolète, vous pouvez maintenant utiliser .on () pour

Joindre un gestionnaire d'événements pour tous les éléments qui correspondent au sélecteur actuel, maintenant et dans le futur

Cela signifie que vous pouvez attacher un événement à un élément parent qui existe toujours et attacher des éléments enfants qu'ils soient présents ou non!

Lorsque vous utilisez .on () comme ceci:

$('#Parent').on('click', '#myButton'  onButtonClicked);

Vous attrapez l'événement en cliquant sur le parent et il recherche l'enfant '#myButton' s'il existe ...

Ainsi, lorsque vous supprimez ou ajoutez un élément enfant, vous n'avez pas à vous soucier d'ajouter ou de supprimer la liaison d'événement.

A. Morel
la source
C'est absolument la meilleure réponse ici pour les normes actuelles. Je ne pense pas que cette fonctionnalité consistant à configurer le gestionnaire sur le parent pour surveiller l'enfant a été bien annoncée, mais c'est une solution plutôt élégante. J'étais en train de déclencher un événement plusieurs fois et à chaque fois, le nombre total de fois qu'il a tiré n'a cessé d'augmenter. Cette seule ligne a fait tout le tour et sans utiliser .offce qui, je pense, supprimerait les gestionnaires non liés dans le processus.
Xandor
1

Essayer:

if (typeof($("#myButton").click) != "function") 
{
   $("#myButton").click(onButtonClicked);
}
Mukhtiar Zamin
la source
0
if ($("#btn").data('events') != undefined && $("#btn").data('events').click != undefined) {
    //do nothing as the click event is already there
} else {
    $("#btn").click(function (e) {
        alert('test');
    });
}
Bishoy Hanna
la source
0

Depuis juin 2019, j'ai mis à jour la fonction (et elle fonctionne pour ce dont j'ai besoin)

$.fn.isBound = function (type) {
    var data = $._data($(this)[0], 'events');

    if (data[type] === undefined || data.length === 0) {
        return false;
    }
    return true;
};
Carlos Casalicchio
la source
-2

JQuery a une solution :

$( "#foo" ).one( "click", function() {
  alert( "This will be displayed only once." );
}); 

équivalent:

$( "#foo" ).on( "click", function( event ) {
  alert( "This will be displayed only once." );
  $( this ).off( event );
});
Veniamin
la source
1
Votre réponse n'est pas liée à la question. La question concerne la liaison de la fonction à l'événement de l'élément une seule fois.
Michał Zalewski