Comment commander des événements liés avec jQuery

164

Disons que j'ai une application Web qui a une page qui peut contenir 4 blocs de script - le script que j'écris peut être trouvé dans l'un de ces blocs, mais je ne sais pas lequel, qui est géré par le contrôleur.

Je lie certains onclickévénements à un bouton, mais je trouve qu'ils s'exécutent parfois dans un ordre auquel je ne m'attendais pas.

Existe-t-il un moyen de garantir l'ordre ou comment avez-vous géré ce problème dans le passé?

mkoryak
la source
1
Ajoutez vos rappels à un objet CallBack et lorsque vous souhaitez les déclencher, vous pouvez les déclencher tous en même temps et ils s'exécuteront dans l'ordre ajouté. api.jquery.com/jQuery.Callbacks
Tyddlywink

Réponses:

28

J'essayais depuis des lustres de généraliser ce genre de processus, mais dans mon cas, je n'étais préoccupé que par l'ordre du premier auditeur d'événements de la chaîne.

Si cela est utile, voici mon plugin jQuery qui lie un écouteur d'événement qui est toujours déclenché avant les autres:

** MISE À JOUR en ligne avec les changements de jQuery (merci Toskan) **

(function($) {
    $.fn.bindFirst = function(/*String*/ eventType, /*[Object])*/ eventData, /*Function*/ handler) {
        var indexOfDot = eventType.indexOf(".");
        var eventNameSpace = indexOfDot > 0 ? eventType.substring(indexOfDot) : "";

        eventType = indexOfDot > 0 ? eventType.substring(0, indexOfDot) : eventType;
        handler = handler == undefined ? eventData : handler;
        eventData = typeof eventData == "function" ? {} : eventData;

        return this.each(function() {
            var $this = $(this);
            var currentAttrListener = this["on" + eventType];

            if (currentAttrListener) {
                $this.bind(eventType, function(e) {
                    return currentAttrListener(e.originalEvent); 
                });

                this["on" + eventType] = null;
            }

            $this.bind(eventType + eventNameSpace, eventData, handler);

            var allEvents = $this.data("events") || $._data($this[0], "events");
            var typeEvents = allEvents[eventType];
            var newEvent = typeEvents.pop();
            typeEvents.unshift(newEvent);
        });
    };
})(jQuery);

Choses à noter:

  • Cela n'a pas été entièrement testé.
  • Il repose sur les éléments internes du framework jQuery qui ne changent pas (testé uniquement avec la version 1.5.2).
  • Il ne sera pas nécessairement déclenché avant les écouteurs d'événements qui sont liés d'une manière autre que comme attribut de l'élément source ou en utilisant jQuery bind () et d'autres fonctions associées.
Ed.
la source
Il est important de noter que cela ne fonctionne également que pour les événements qui ont été ajoutés via jQuery.
Grinn
1
cela ne fonctionne plus depuis qu'il utilise this.data("events"), voir ici stackoverflow.com/questions/12214654
...
4
Il existe une fonction similaire qui a été mise à jour pour jQuery 1.8 ici: stackoverflow.com/a/2641047/850782
EpicVoyage
136

Si l'ordre est important, vous pouvez créer vos propres événements et lier les rappels pour qu'ils se déclenchent lorsque ces événements sont déclenchés par d'autres rappels.

$('#mydiv').click(function(e) {
    // maniplate #mydiv ...
    $('#mydiv').trigger('mydiv-manipulated');
});

$('#mydiv').bind('mydiv-manipulated', function(e) {
    // do more stuff now that #mydiv has been manipulated
    return;
});

Quelque chose comme ça au moins.

dowski
la source
23
que se passe-t-il si nous voulons arrêter la propagation de l'événement click pour les rappels définis à un moment donné avant notre code de liaison.
vinilios le
2
Puis-je demander pourquoi cela n'a pas été accepté? La réponse actuellement acceptée cite ma réponse comme la meilleure méthode, donc je suis un peu confus. :-)
dowski
Cela fonctionne pour le cas de mkoryak, mais pas si le même événement est appelé plusieurs fois. J'ai un problème similaire, où une seule touche déclenche $ (window) .scroll () plusieurs fois, dans l' ordre inverse .
kxsong le
37

La méthode de Dowski est bonne si tous vos rappels sont toujours présents et que vous êtes satisfait qu'ils soient dépendants les uns des autres.

Cependant, si vous souhaitez que les rappels soient indépendants les uns des autres, vous pouvez profiter de la création de bulles et attacher des événements ultérieurs en tant que délégués aux éléments parents. Les gestionnaires d'un élément parent seront déclenchés après les gestionnaires de l'élément, en continuant jusqu'au document. Ceci est tout à fait bon que vous pouvez utiliser event.stopPropagation(), event.preventDefault()etc pour sauter les gestionnaires et annuler ou non annuler l'action.

$( '#mybutton' ).click( function(e) { 
    // Do stuff first
} );

$( '#mybutton' ).click( function(e) { 
    // Do other stuff first
} );

$( document ).delegate( '#mybutton', 'click', function(e) {
    // Do stuff last
} );

Ou, si vous n'aimez pas cela, vous pouvez utiliser le plugin Nick Leaches bindLast pour forcer un événement à être lié en dernier: https://github.com/nickyleach/jQuery.bindLast .

Ou, si vous utilisez jQuery 1.5, vous pouvez également faire quelque chose d'intelligent avec le nouvel objet Deferred.

Daniel Lewis
la source
6
.delegaten'est plus utilisé et vous devriez maintenant l'utiliser .on, mais je ne sais pas comment vous pouvez atteindre le même comportement avecon
eric.itzhak
le plugin doit être corrigé car il ne fonctionne plus voir ici: stackoverflow.com/questions/12214654
...
@ eric.itzhak Pour que .on () se comporte comme l'ancien .delegate (), fournissez-lui simplement un sélecteur. Détails ici api.jquery.com/delegate
Sam Kauffman
Si vous voulez profiter du bouillonnement, je pense que l'événement doit être direct plutôt que délégué . api.jquery.com/on/#direct-and-delegated-events
Ryne Everett
15

L'ordre dans lequel les rappels liés sont appelés est géré par les données d'événement de chaque objet jQuery. Il n'y a pas de fonctions (que je connaisse) qui vous permettent de visualiser et de manipuler ces données directement, vous ne pouvez utiliser que bind () et unbind () (ou l'une des fonctions d'assistance équivalentes).

La méthode de Dowski est la meilleure, vous devez modifier les divers rappels liés pour les lier à une séquence ordonnée d'événements personnalisés, avec le "premier" rappel lié à l'événement "réel". De cette façon, quel que soit l'ordre dans lequel ils sont liés, la séquence s'exécutera de la bonne manière.

La seule alternative que je peux voir est quelque chose que vous ne voulez vraiment, vraiment pas envisager: si vous connaissez la syntaxe de liaison des fonctions peut avoir été liée avant vous, essayez de dissocier toutes ces fonctions, puis de les relier à nouveau dans le bon ordre vous-même. Cela pose juste des problèmes, car maintenant vous avez du code en double.

Ce serait cool si jQuery vous permettait de changer simplement l'ordre des événements liés dans les données d'événement d'un objet, mais sans écrire du code à accrocher dans le noyau jQuery, ce qui ne semble pas possible. Et il y a probablement des implications à autoriser cela auxquelles je n'ai pas pensé, alors peut-être que c'est une omission intentionnelle.

Adam Bellaire
la source
12
pour voir tous les événements liés à un élément par jquery, utilisez var allEvents = $ .data (this, "events");
redsquare
9

Veuillez noter que dans l'univers jQuery, cela doit être implémenté différemment à partir de la version 1.8. La note de publication suivante est tirée du blog jQuery:

.data («events»): jQuery stocke ses données liées aux événements dans un objet de données nommé (attendez) les événements sur chaque élément. Il s'agit d'une structure de données interne, donc dans la version 1.8, celle-ci sera supprimée de l'espace de nom des données utilisateur afin de ne pas entrer en conflit avec les éléments du même nom. Les données d'événement de jQuery sont toujours accessibles via jQuery._data (élément, "events")

Nous avons un contrôle complet sur l'ordre dans lequel les gestionnaires s'exécuteront dans l'univers jQuery . Ricoo le souligne ci-dessus. Il ne semble pas que sa réponse lui ait valu beaucoup d'amour, mais cette technique est très pratique. Considérez, par exemple, chaque fois que vous devez exécuter votre propre gestionnaire avant un gestionnaire dans un widget de bibliothèque, ou vous devez avoir le pouvoir d'annuler l'appel au gestionnaire du widget de manière conditionnelle:

$("button").click(function(e){
    if(bSomeConditional)
       e.stopImmediatePropagation();//Don't execute the widget's handler
}).each(function () {
    var aClickListeners = $._data(this, "events").click;
    aClickListeners.reverse();
});
hochet de cage
la source
8
function bindFirst(owner, event, handler) {
    owner.unbind(event, handler);
    owner.bind(event, handler);

    var events = owner.data('events')[event];
    events.unshift(events.pop());

    owner.data('events')[event] = events;
}
merle
la source
7

liez simplement le gestionnaire normalement, puis exécutez:

element.data('events').action.reverse();

donc par exemple:

$('#mydiv').data('events').click.reverse();
Tomasbedrich
la source
2
ne fonctionne pas, mais cela $._data(element, 'events')[name].reverse()fonctionne
Attenzione
3

Vous pouvez essayer quelque chose comme ceci:

/**
  * Guarantee that a event handler allways be the last to execute
  * @param owner The jquery object with any others events handlers $(selector)
  * @param event The event descriptor like 'click'
  * @param handler The event handler to be executed allways at the end.
**/
function bindAtTheEnd(owner,event,handler){
    var aux=function(){owner.unbind(event,handler);owner.bind(event,handler);};
    bindAtTheStart(owner,event,aux,true);

}
/**
  * Bind a event handler at the start of all others events handlers.
  * @param owner Jquery object with any others events handlers $(selector);
  * @param event The event descriptor for example 'click';
  * @param handler The event handler to bind at the start.
  * @param one If the function only be executed once.
**/
function bindAtTheStart(owner,event,handler,one){
    var eventos,index;
    var handlers=new Array();
    owner.unbind(event,handler);
    eventos=owner.data("events")[event];
    for(index=0;index<eventos.length;index+=1){
        handlers[index]=eventos[index];
    }
    owner.unbind(event);
    if(one){
        owner.one(event,handler);
    }
    else{
        owner.bind(event,handler);
    }
    for(index=0;index<handlers.length;index+=1){
        owner.bind(event,ownerhandlers[index]);
    }   
}
bleu sauvage
la source
Je pense que les «propriétaires» ne devraient être que des «gestionnaires»
Joril
1

J'ai le même problème et j'ai trouvé ce sujet. les réponses ci-dessus peuvent résoudre ces problèmes, mais je ne pense pas que ce soient de bons plans.

pensons au monde réel.

si nous utilisons ces réponses, nous devons changer notre code. vous devez changer votre style de code. quelque chose comme ça:

original:

$('form').submit(handle);

pirater:

bindAtTheStart($('form'),'submit',handle);

au fil du temps, pensez à votre projet. le code est moche et difficile à lire! la raison est simple, c'est toujours mieux. si vous avez 10 bindAtTheStart, il se peut qu'il n'y ait pas de bogues. si vous avez 100 bindAtTheStart, êtes-vous vraiment sûr de pouvoir les conserver dans le bon ordre?

Donc, si vous devez lier plusieurs événements, je pense que le meilleur moyen est de contrôler l'ordre de chargement du fichier js ou du code js. jquery peut gérer les données d'événement en tant que file d'attente. l'ordre est le premier entré, premier sorti. vous n'avez pas besoin de changer de code. changez simplement l'ordre de chargement.

9nix00
la source
0

Voici ma photo, couvrant différentes versions de jQuery:

// Binds a jQuery event to elements at the start of the event chain for that type.
jQuery.extend({
    _bindEventHandlerAtStart: function ($elements, eventType, handler) {
        var _data;

        $elements.bind(eventType, handler);
        // This bound the event, naturally, at the end of the event chain. We
        // need it at the start.

        if (typeof jQuery._data === 'function') {
            // Since jQuery 1.8.1, it seems, that the events object isn't
            // available through the public API `.data` method.
            // Using `$._data, where it exists, seems to work.
            _data = true;
        }

        $elements.each(function (index, element) {
            var events;

            if (_data) {
                events = jQuery._data(element, 'events')[eventType];
            } else {
                events = jQuery(element).data('events')[eventType];
            }

            events.unshift(events.pop());

            if (_data) {
                jQuery._data(element, 'events')[eventType] = events;
            } else {
                jQuery(element).data('events')[eventType] = events;
            }
        });
    }
});
mightyiam
la source
0

Dans certains cas particuliers, lorsque vous ne pouvez pas modifier la façon dont les événements de clic sont liés (les liaisons d'événements sont faites à partir des codes des autres), et que vous pouvez modifier l'élément HTML, voici une solution possible ( avertissement : ce n'est pas la manière recommandée de lier événements, d'autres développeurs peuvent vous assassiner pour cela):

<span onclick="yourEventHandler(event)">Button</span>

Avec cette méthode de liaison, votre gestionnaire d'événement sera ajouté en premier, il sera donc exécuté en premier.

Barrage de Linh
la source
Ce n'est pas une solution, c'est une allusion à une solution. Suggérez-vous que ce gestionnaire "onclick" utilise l'élément qui a déjà un gestionnaire (jQuery) ou est-ce que votre <span> encapsule l'élément avec le gestionnaire jQuery?
Auspex
-2

JQuery 1.5 introduit des promesses, et voici l'implémentation la plus simple que j'ai vue pour contrôler l'ordre d'exécution. Documentation complète sur http://api.jquery.com/jquery.when/

$.when( $('#myDiv').css('background-color', 'red') )
 .then( alert('hi!') )
 .then( myClickFunction( $('#myID') ) )
 .then( myThingToRunAfterClick() );
Tanya Sweeney
la source