Jak porządkować zdarzenia związane z jQuery

164

Powiedzmy, że mam aplikację internetową, która ma stronę, która może zawierać 4 bloki skryptów - skrypt, który piszę, może znajdować się w jednym z tych bloków, ale nie wiem, który z nich jest obsługiwany przez kontroler.

Powiązuję niektóre onclickzdarzenia z przyciskiem, ale okazuje się, że czasami są one wykonywane w kolejności, której się nie spodziewałem.

Czy istnieje sposób na zapewnienie porządku lub jak radziłeś sobie z tym problemem w przeszłości?

mkoryak
źródło
1
Dodaj swoje wywołania zwrotne do obiektu CallBack, a jeśli chcesz je odpalić, możesz uruchomić je wszystkie naraz i będą działać w dodanej kolejności. api.jquery.com/jQuery.Callbacks
Tyddlywink,

Odpowiedzi:

28

Od dawna próbowałem uogólnić ten rodzaj procesu, ale w moim przypadku interesowała mnie tylko kolejność pierwszego w łańcuchu słuchacza.

Jeśli jest to przydatne, oto moja wtyczka jQuery, która wiąże detektor zdarzeń, który jest zawsze wyzwalany przed innymi:

** ZAKTUALIZOWANY inline ze zmianami jQuery (dzięki 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);

Kwestie, na które należy zwrócić uwagę:

  • To nie zostało w pełni przetestowane.
  • Opiera się na niezmienności wewnętrznych elementów frameworka jQuery (testowane tylko w wersji 1.5.2).
  • Niekoniecznie zostanie wyzwolony przed detektorami zdarzeń, które są powiązane w jakikolwiek inny sposób niż jako atrybut elementu źródłowego lub za pomocą jQuery bind () i innych powiązanych funkcji.
Ed.
źródło
Należy zauważyć, że działa to również tylko w przypadku wydarzeń, które zostały dodane za pośrednictwem jQuery.
Grinn
1
to już nie działa, ponieważ używa this.data("events"), patrz tutaj stackoverflow.com/questions/12214654/ ...
Toskan
4
Istnieje podobna funkcja, która została zaktualizowana dla jQuery 1.8 tutaj: stackoverflow.com/a/2641047/850782
EpicVoyage
136

Jeśli kolejność jest ważna, możesz tworzyć własne zdarzenia i przypisywać wywołania zwrotne do uruchamiania, gdy te zdarzenia są wyzwalane przez inne wywołania zwrotne.

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

Przynajmniej coś takiego.

dowski
źródło
23
co, jeśli chcemy zatrzymać propagację zdarzenia kliknięcia dla wywołań zwrotnych zdefiniowanych w pewnym momencie przed naszym kodem powiązania.
vinilios
2
Czy mogę zapytać, dlaczego to nie zostało zaakceptowane? Obecnie akceptowana odpowiedź cytuje moją odpowiedź jako najlepszą, więc jestem trochę zdezorientowany. :-)
dowski
Działa to w przypadku mkoryaka, ale nie wtedy, gdy to samo zdarzenie jest wywoływane wiele razy. Mam podobny problem, w którym pojedyncze naciśnięcie klawisza wyzwala $ (okno) .scroll () wiele razy, w odwrotnej kolejności.
kxsong
37

Metoda Dowskiego jest dobra, jeśli wszystkie twoje callbacki będą zawsze obecne i jesteś zadowolony, że są od siebie zależni.

Jeśli jednak chcesz, aby wywołania zwrotne były od siebie niezależne, możesz skorzystać z propagacji i dołączać kolejne zdarzenia jako delegaty do elementów nadrzędnych. Procedury obsługi elementu nadrzędnego zostaną wyzwolone po funkcjach obsługi elementu, kontynuując aż do dokumentu. Jest to całkiem dobre, ponieważ możesz użyć event.stopPropagation(), event.preventDefault()etc, aby pominąć programy obsługi i anulować lub anulować akcję.

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

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

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

Lub, jeśli ci się to nie podoba, możesz użyć wtyczki Nick Leaches bindLast, aby zmusić wydarzenie do ostatniego powiązania: https://github.com/nickyleach/jQuery.bindLast .

Lub, jeśli używasz jQuery 1.5, możesz również potencjalnie zrobić coś sprytnego z nowym obiektem Deferred.

Daniel Lewis
źródło
6
.delegatenie jest już używany i powinieneś go teraz używać .on, ale nie wiem, jak możesz osiągnąć to samo zachowanie zon
eric.itzhak
wtyczka musi zostać naprawiona, ponieważ już nie działa, patrz tutaj: stackoverflow.com/questions/12214654/ ...
Toskan
@ eric.itzhak Aby .on () zachowywał się jak stary .delegate (), wystarczy zapewnić mu selektor. Szczegóły tutaj: api.jquery.com/delegate
Sam Kauffman
Jeśli chcesz skorzystać z bulgotania, myślę, że wydarzenie powinno być bezpośrednie, a nie delegowane . api.jquery.com/on/#direct-and-delegated-events
Ryne Everett
15

Kolejność wywoływania powiązanych wywołań zwrotnych jest zarządzana przez dane zdarzenia każdego obiektu jQuery. Nie ma żadnych funkcji (o których wiem), które pozwalają na bezpośrednie przeglądanie i manipulowanie tymi danymi, możesz używać tylko bind () i unbind () (lub dowolnej z równoważnych funkcji pomocniczych).

Metoda Dowskiego jest najlepsza, powinieneś zmodyfikować różne powiązane wywołania zwrotne, aby powiązać je z uporządkowaną sekwencją niestandardowych zdarzeń, z „pierwszym” wywołaniem zwrotnym powiązanym ze „rzeczywistym” zdarzeniem. W ten sposób, bez względu na to, w jakiej kolejności są one związane, sekwencja zostanie wykonana we właściwy sposób.

Jedyną alternatywą, jaką widzę, jest coś, nad czym naprawdę nie chcesz się zastanawiać: jeśli wiesz, że składnia wiążąca funkcji mogła być wcześniej związana, spróbuj usunąć powiązanie wszystkich tych funkcji, a następnie ponownie je powiązać we właściwej kolejności. To tylko prośba o kłopoty, ponieważ masz teraz zduplikowany kod.

Byłoby fajnie, gdyby jQuery pozwolił ci po prostu zmienić kolejność powiązanych zdarzeń w danych zdarzenia obiektu, ale bez pisania jakiegoś kodu, aby podłączyć się do rdzenia jQuery, co wydaje się niemożliwe. I prawdopodobnie pozwolenie na to ma implikacje, o których nie pomyślałem, więc może jest to celowe pominięcie.

Adam Bellaire
źródło
12
aby zobaczyć wszystkie zdarzenia powiązane z elementem przez jquery użyj var allEvents = $ .data (this, "events");
redsquare
9

Należy pamiętać, że w świecie jQuery musi to zostać zaimplementowane inaczej od wersji 1.8. Poniższa informacja o wersji pochodzi z bloga jQuery:

.data („zdarzenia”): jQuery przechowuje swoje dane związane ze zdarzeniami w obiekcie danych o nazwie (czekaj na to) zdarzenia w każdym elemencie. To jest wewnętrzna struktura danych, więc w 1.8 zostanie ona usunięta z przestrzeni nazw danych użytkownika, więc nie będzie kolidować z elementami o tej samej nazwie. Dostęp do danych zdarzeń jQuery można nadal uzyskać za pośrednictwem jQuery._data (element, „zdarzenia”)

Mamy pełną kontrolę nad kolejnością, w jakiej procedury obsługi będą wykonywane we wszechświecie jQuery . Ricoo zwraca na to uwagę powyżej. Nie wygląda na to, żeby jego odpowiedź przyniosła mu dużo miłości, ale ta technika jest bardzo przydatna. Rozważ na przykład za każdym razem, gdy musisz wykonać własny program obsługi przed jakimś programem obsługi w widgecie biblioteki lub musisz mieć możliwość warunkowego anulowania wywołania funkcji obsługi widgetu:

$("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();
});
klatka grzechotnik
źródło
7

po prostu połącz obsługę normalnie, a następnie uruchom:

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

więc na przykład:

$('#mydiv').data('events').click.reverse();
tomasbedrich
źródło
2
nie działa, ale to $._data(element, 'events')[name].reverse()działa
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;
}
rudzik
źródło
3

Możesz spróbować czegoś takiego:

/**
  * 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]);
    }   
}
dziki niebieski
źródło
Myślę, że „właścicielami” powinni być tylko „opiekunowie”
Joril
1

Mam ten sam problem i znalazłem ten temat. powyższe odpowiedzi mogą rozwiązać ten problem, ale nie sądzę, że są to dobre plany.

pomyślmy o prawdziwym świecie.

jeśli korzystamy z tych odpowiedzi, musimy zmienić nasz kod. musisz zmienić swój styl kodu. coś takiego:

oryginalny:

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

włamać się:

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

w miarę upływu czasu pomyśl o swoim projekcie. kod jest brzydki i trudny do odczytania! inny powód jest prosty, jest zawsze lepszy. jeśli masz 10 bindAtTheStart, może to nie zawierać błędów. jeśli masz 100 bindAtTheStart, czy na pewno możesz zachować je we właściwej kolejności?

więc jeśli musisz powiązać te same zdarzenia z wieloma. Myślę, że najlepszym sposobem jest kontrola kolejności ładowania pliku js lub kodu js. jquery może obsługiwać dane zdarzenia jako kolejkę. kolejność jest pierwsza weszła, pierwsza wyszła. nie musisz zmieniać żadnego kodu. po prostu zmień kolejność ładowania.

9nix00
źródło
0

Oto moje ujęcie, obejmujące różne wersje 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
źródło
0

W niektórych specjalnych przypadkach, gdy nie możesz zmienić sposobu powiązania zdarzeń kliknięcia (powiązania zdarzeń są tworzone z kodów innych osób) i możesz zmienić element HTML, oto możliwe rozwiązanie ( ostrzeżenie : nie jest to zalecany sposób łączenia wydarzenia, inni programiści mogą cię za to zamordować):

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

Dzięki temu sposobowi łączenia, twoja obsługa zdarzeń zostanie dodana jako pierwsza, więc zostanie wykonana jako pierwsza.

Linh Dam
źródło
To nie jest rozwiązanie, to wskazówka na rozwiązanie. Sugeruje pan, że to „onclick” Wózek jedzie na elemencie, który ma już (jQuery) obsługi, albo jest Twój <span> owijania elementu z obsługi jQuery?
Auspex
-2

JQuery 1.5 wprowadza obietnice, a oto najprostsza implementacja, jaką widziałem, kontrolująca kolejność wykonywania. Pełna dokumentacja pod adresem http://api.jquery.com/jquery.when/

$.when( $('#myDiv').css('background-color', 'red') )
 .then( alert('hi!') )
 .then( myClickFunction( $('#myID') ) )
 .then( myThingToRunAfterClick() );
Tanya Sweeney
źródło