So bestellen Sie mit jQuery gebundene Ereignisse


164

Nehmen wir an, ich habe eine Web-App mit einer Seite, die 4 Skriptblöcke enthalten kann. Das Skript, das ich schreibe, befindet sich möglicherweise in einem dieser Blöcke, aber ich weiß nicht, welches vom Controller verarbeitet wird.

Ich binde einige onclickEreignisse an eine Schaltfläche, stelle jedoch fest, dass sie manchmal in einer Reihenfolge ausgeführt werden, die ich nicht erwartet hatte.

Gibt es eine Möglichkeit, die Ordnung sicherzustellen, oder wie haben Sie dieses Problem in der Vergangenheit behandelt?


1
Fügen Sie Ihre Rückrufe einem CallBack-Objekt hinzu. Wenn Sie sie auslösen möchten, können Sie sie alle gleichzeitig auslösen, und sie werden in der hinzugefügten Reihenfolge ausgeführt. api.jquery.com/jQuery.Callbacks
Tyddlywink

Antworten:


28

Ich hatte lange versucht, diese Art von Prozess zu verallgemeinern, aber in meinem Fall ging es mir nur um die Reihenfolge des ersten Ereignis-Listeners in der Kette.

Wenn es von Nutzen ist, ist hier mein jQuery-Plugin, das einen Ereignis-Listener bindet, der immer vor allen anderen ausgelöst wird:

** AKTUALISIERT im Einklang mit jQuery-Änderungen (danke 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);

Dinge zu beachten:

  • Dies wurde nicht vollständig getestet.
  • Es basiert darauf, dass sich die Interna des jQuery-Frameworks nicht ändern (nur mit 1.5.2 getestet).
  • Es wird nicht unbedingt vor Ereignis-Listenern ausgelöst, die auf andere Weise als als Attribut des Quellelements gebunden sind oder jQuery bind () und andere zugehörige Funktionen verwenden.

Es ist wichtig zu beachten, dass dies auch nur für Ereignisse funktioniert, die über jQuery hinzugefügt wurden.
Grinn

1
Dies funktioniert nicht mehr, da es verwendet wird this.data("events"), siehe hier stackoverflow.com/questions/12214654/…
Toskan

4
Es gibt eine ähnliche Funktion, die für jQuery 1.8 hier aktualisiert wurde: stackoverflow.com/a/2641047/850782
EpicVoyage

136

Wenn die Reihenfolge wichtig ist, können Sie Ihre eigenen Ereignisse erstellen und Rückrufe an das Feuer binden, wenn diese Ereignisse durch andere Rückrufe ausgelöst werden.

$('#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;
});

Zumindest so etwas.


23
Was ist, wenn wir die Weitergabe des Klickereignisses für Rückrufe stoppen möchten, die irgendwann vor unserem Bindecode definiert wurden?
Vinilios

2
Kann ich fragen, warum dies nicht akzeptiert wurde? In der aktuell akzeptierten Antwort wird meine Antwort als die beste Methode angegeben, daher bin ich etwas verwirrt. :-)
Dowski

Dies funktioniert für mkoryaks Fall, jedoch nicht, wenn dasselbe Ereignis mehrmals aufgerufen wird. Ich habe ein ähnliches Problem, bei dem ein einzelner Tastendruck $ (window) .scroll () mehrmals in umgekehrter Reihenfolge auslöst .
kxsong

37

Dowskis Methode ist gut, wenn alle Ihre Rückrufe immer vorhanden sind und Sie zufrieden sind, dass sie voneinander abhängig sind.

Wenn Sie jedoch möchten, dass die Rückrufe unabhängig voneinander sind, können Sie das Sprudeln nutzen und nachfolgende Ereignisse als Delegaten an übergeordnete Elemente anhängen. Die Handler für übergeordnete Elemente werden nach den Handlern für das Element ausgelöst und bis zum Dokument fortgesetzt. Das ist ziemlich gut , wie Sie verwenden können event.stopPropagation(), event.preventDefault()etc Handler zu überspringen und stornieren oder un-brechen Sie die Aktion.

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

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

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

Wenn Ihnen das nicht gefällt, können Sie das bindLast-Plugin von Nick Leaches verwenden, um zu erzwingen, dass ein Ereignis zuletzt gebunden wird: https://github.com/nickyleach/jQuery.bindLast .

Wenn Sie jQuery 1.5 verwenden, können Sie mit dem neuen Objekt "Zurückgestellt" möglicherweise auch etwas Kluges tun.


6
.delegatewird nicht mehr verwendet und Sie sollten jetzt verwenden .on, aber ich weiß nicht, wie Sie das gleiche Verhalten miton
eric.itzhak

Das Plugin muss repariert werden,
Toskan

@ eric.itzhak Damit sich .on () wie das alte .delegate () verhält, geben Sie ihm einfach einen Selektor. Details hier api.jquery.com/delegate
Sam Kauffman

Wenn Sie das Sprudeln nutzen möchten, muss die Veranstaltung meiner Meinung nach eher direkt als delegiert sein . api.jquery.com/on/#direct-and-delegated-events
Ryne Everett

15

Die Reihenfolge, in der die gebundenen Rückrufe aufgerufen werden, wird von den Ereignisdaten jedes jQuery-Objekts verwaltet. Es gibt keine Funktionen (die mir bekannt sind), mit denen Sie diese Daten direkt anzeigen und bearbeiten können. Sie können nur bind () und unbind () (oder eine der entsprechenden Hilfsfunktionen) verwenden.

Dowskis Methode ist am besten, Sie sollten die verschiedenen gebundenen Rückrufe so ändern, dass sie an eine geordnete Folge von benutzerdefinierten Ereignissen gebunden sind, wobei der "erste" Rückruf an das "echte" Ereignis gebunden ist. Auf diese Weise wird die Sequenz unabhängig von der Reihenfolge, in der sie gebunden sind, auf die richtige Weise ausgeführt.

Die einzige Alternative, die ich sehen kann, ist etwas, über das Sie wirklich, wirklich nicht nachdenken möchten: Wenn Sie wissen, dass die Bindungssyntax der Funktionen möglicherweise vor Ihnen gebunden wurde, versuchen Sie, alle diese Funktionen zu lösen und sie dann erneut zu binden in der richtigen Reihenfolge. Das ist nur ein Problem, denn jetzt haben Sie Code dupliziert.

Es wäre cool, wenn Sie mit jQuery einfach die Reihenfolge der gebundenen Ereignisse in den Ereignisdaten eines Objekts ändern könnten, ohne jedoch Code zum Einbinden in den jQuery-Kern zu schreiben, der nicht möglich erscheint. Und es gibt wahrscheinlich Implikationen, dies zuzulassen, an die ich nicht gedacht habe. Vielleicht ist es eine absichtliche Auslassung.


12
Um alle Ereignisse anzuzeigen, die durch jquery an ein Element gebunden sind, verwenden Sie var allEvents = $ .data (this, "events").
Redsquare

9

Bitte beachten Sie, dass dies im jQuery-Universum ab Version 1.8 anders implementiert werden muss. Der folgende Versionshinweis stammt aus dem jQuery-Blog:

.data („Ereignisse“): jQuery speichert seine ereignisbezogenen Daten in einem Datenobjekt mit dem Namen (darauf warten) Ereignisse für jedes Element. Dies ist eine interne Datenstruktur, daher wird diese in Version 1.8 aus dem Namensraum für Benutzerdaten entfernt, damit keine Konflikte mit gleichnamigen Elementen auftreten. Auf die Ereignisdaten von jQuery kann weiterhin über jQuery._data (Element, "Ereignisse") zugegriffen werden.

Wir haben die vollständige Kontrolle über die Reihenfolge, in der die Handler im jQuery-Universum ausgeführt werden . Ricoo weist oben darauf hin. Sieht nicht so aus, als hätte ihm seine Antwort viel Liebe eingebracht, aber diese Technik ist sehr praktisch. Betrachten Sie beispielsweise jedes Mal, wenn Sie Ihren eigenen Handler vor einem Handler in einem Bibliotheks-Widget ausführen müssen, oder wenn Sie die Möglichkeit haben müssen, den Aufruf des Handlers des Widgets unter bestimmten Bedingungen abzubrechen:

$("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();
});

7

Binden Sie den Handler einfach normal und führen Sie dann Folgendes aus:

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

so zum Beispiel:

$('#mydiv').data('events').click.reverse();

2
funktioniert nicht, aber das $._data(element, 'events')[name].reverse()funktioniert
Attenzione

7
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;
}

3

Sie können so etwas ausprobieren:

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

Ich denke, "Besitzerhandler" sollten nur "Handler" sein
Joril

1

Ich habe das gleiche Problem und habe dieses Thema gefunden. Die obigen Antworten können dieses Problem lösen, aber ich denke nicht, dass sie gute Pläne sind.

Lassen Sie uns über die reale Welt nachdenken.

Wenn wir diese Antworten verwenden, müssen wir unseren Code ändern. Sie müssen Ihren Codestil ändern. etwas wie das:

Original:

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

hacken:

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

Denken Sie im Laufe der Zeit an Ihr Projekt. Der Code ist hässlich und schwer zu lesen! Anthoer Grund ist einfach ist immer besser. Wenn Sie 10 bindAtTheStart haben, kann es keine Fehler geben. Wenn Sie 100 bindAtTheStart haben, sind Sie wirklich sicher, dass Sie sie in der richtigen Reihenfolge halten können?

Wenn Sie also dieselben Ereignisse mehrfach binden müssen. Ich denke, der beste Weg ist, die Ladereihenfolge für js-Datei oder js-Code zu steuern. jquery kann Ereignisdaten als Warteschlange verarbeiten. Die Reihenfolge ist First-In, First-Out. Sie müssen keinen Code ändern. Ändern Sie einfach die Ladereihenfolge.


0

Hier ist meine Aufnahme, die verschiedene Versionen von jQuery abdeckt:

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

In einigen besonderen Fällen, in denen Sie die Bindung der Klickereignisse nicht ändern können (Ereignisbindungen werden aus den Codes anderer erstellt) und das HTML-Element ändern können, ist hier eine mögliche Lösung ( Warnung : Dies ist nicht die empfohlene Methode zum Binden Ereignisse, andere Entwickler können Sie dafür ermorden):

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

Bei dieser Art der Bindung wird Ihr Event-Hander zuerst hinzugefügt, sodass er zuerst ausgeführt wird.


Das ist keine Lösung, es ist ein Hinweis auf eine Lösung. Behaupten Sie , dass diese „Onclick“ Handler auf dem Element wird , dass bereits eine (jQuery) Handler hat, oder ist Ihr <span> Einwickeln das Element mit dem jQuery - Handler?
Auspex

-2

JQuery 1.5 führt Versprechen ein, und hier ist die einfachste Implementierung, die ich gesehen habe, um die Ausführungsreihenfolge zu steuern. Vollständige Dokumentation unter http://api.jquery.com/jquery.when/

$.when( $('#myDiv').css('background-color', 'red') )
 .then( alert('hi!') )
 .then( myClickFunction( $('#myID') ) )
 .then( myThingToRunAfterClick() );
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.