web-dev-qa-db-fra.com

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

Disons que j'ai une application Web qui a une page pouvant 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 est géré par le contrôleur.

Je lie certains événements onclick à un bouton, mais j’aperçois qu’ils s’exécutent parfois dans un ordre inattendu.

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

163
mkoryak

Cela faisait très longtemps que j'essayais de généraliser ce type de processus, mais dans mon cas, je ne m'occupais que de l'ordre du premier auditeur de la chaîne.

Si cela peut être utile, voici mon plugin jQuery qui lie un écouteur d'événements toujours déclenché avant les autres:

** UPDATED en ligne avec les modifications 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é complètement testé.
  • Cela repose sur le fait que les éléments internes du framework jQuery ne changent pas (seulement testé avec 1.5.2).
  • Il ne sera pas nécessairement déclenché avant les écouteurs d'événements liés autrement que comme attribut de l'élément source ou utilisant jQuery bind () et d'autres fonctions associées.
27
Ed .

Si l'ordre est important, vous pouvez créer vos propres événements et lier des rappels à déclencher 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.

134
dowski

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

Toutefois, si vous souhaitez que les rappels soient indépendants les uns des autres, vous pouvez tirer parti de la propagation des bulles et attacher des événements ultérieurs en tant que délégués aux éléments parents. Les gestionnaires d’éléments parents seront déclenchés après les gestionnaires d’élément, jusqu’au document. C’est très bien, car vous pouvez utiliser event.stopPropagation(), event.preventDefault(), etc. pour ignorer les gestionnaires et annuler ou 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 bindLast de Nick Leaches pour forcer un événement à être lié en dernier: https://github.com/nickyleach/jQuery.bindLast .

Ou, si vous utilisez jQuery 1.5, vous pouvez aussi potentiellement faire quelque chose d'intelligent avec le nouvel objet Différé.

37
Daniel Lewis

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. À ma connaissance, aucune fonction ne vous permet 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 afin de les lier à une séquence ordonnée d'événements personnalisés, le "premier" rappel étant lié à l'événement "réel". Ainsi, 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 puisse voir est quelque chose que vous ne voulez vraiment pas, vraiment: si vous savez que la syntaxe de liaison des fonctions a peut-être été liée, essayez de détacher toutes ces fonctions, puis de les relier à nouveau. dans le bon ordre vous-même. C'est juste demander des ennuis, parce que maintenant vous avez du code dupliqué.

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 à connecter au noyau de jQuery, cela ne semble pas possible. Et il y a probablement des implications à permettre cela, auxquelles je n'ai pas pensé, alors c'est peut-être une omission intentionnelle.

15
Adam Bellaire

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 provient de le blog jQuery:

.data (“events”): jQuery stocke ses données relatives aux événements dans un objet de données nommé (attendez-le), événements sur chaque élément. Il s’agit d’une structure de données interne. En 1.8, elle sera supprimée de l’espace de noms de données de l’utilisateur afin qu’elle n’entre pas 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, "événements")

Nous avons le contrôle complet de l'ordre d'exécution des gestionnaires dans l'univers jQuery . Ricoo le souligne ci-dessus. Sa réponse ne lui a pas 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 que vous ayez le pouvoir d'annuler l'appel du 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();
});
9
cage rattler

juste lier le gestionnaire normalement et ensuite exécuter:

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

ainsi par exemple:

$('#mydiv').data('events').click.reverse();
7
tomasbedrich
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;
}
7
robin

Vous pouvez essayer quelque chose comme ça:

/**
  * 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]);
    }   
}
3
wildblue

J'ai le même problème et a 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);

avec le temps, réfléchissez à votre projet. le code est moche et difficile à lire! anthoer raison est simple est toujours mieux. Si vous avez 10 bindAtTheStart, il ne peut y avoir de bugs. Si vous avez 100 bindAtTheStart, êtes-vous sûr de pouvoir les garder dans le bon ordre?

donc si vous devez lier plusieurs événements identiques.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 file d'attente. la commande est premier entré, premier sorti. vous n'avez pas besoin de changer de code. il suffit de changer l'ordre de chargement.

1
9nix00

Voici ce que je tente de faire, 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;
            }
        });
    }
});
0
mightyiam

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 établies à partir des codes d'autres personnes) et que vous pouvez modifier l'élément HTML, voici une solution possible (warning: this est not le moyen recommandé pour lier des événements, d’autres développeurs peuvent vous assassiner pour cela):

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

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

0
Linh Dam