Come ordinare eventi associati a jQuery


164

Diciamo che ho un'app Web che ha una pagina che può contenere 4 blocchi di script: lo script che scrivo può essere trovato in uno di quei blocchi, ma non so quale sia gestito dal controller.

Associo alcuni onclickeventi a un pulsante, ma trovo che a volte vengano eseguiti in un ordine che non mi aspettavo.

C'è un modo per garantire l'ordine o come hai gestito questo problema in passato?


1
Aggiungi i tuoi callback a un oggetto CallBack e quando vuoi spararli puoi spararli tutti in una volta e verranno eseguiti nell'ordine aggiunto. api.jquery.com/jQuery.Callbacks
Tyddlywink l'

Risposte:


28

Ho cercato per anni di generalizzare questo tipo di processo, ma nel mio caso mi occupavo solo dell'ordine del primo ascoltatore di eventi nella catena.

Se è di qualche utilità, ecco il mio plugin jQuery che lega un listener di eventi che viene sempre attivato prima di qualsiasi altro:

** AGGIORNATO in linea con le modifiche di jQuery (grazie 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);

Cose da notare:

  • Questo non è stato completamente testato.
  • Si basa sul fatto che gli interni del framework jQuery non cambiano (testato solo con 1.5.2).
  • Non verrà necessariamente attivato prima dei listener di eventi associati in alcun modo se non come attributo dell'elemento source o utilizzando jQuery bind () e altre funzioni associate.

È importante notare che questo funziona anche solo per eventi che sono stati aggiunti tramite jQuery.
Grinn,

1
questo non funziona più in quanto utilizza this.data("events"), vedi qui stackoverflow.com/questions/12214654/...~~V~~singular~~3rd
Toskan

4
C'è una funzione simile che è stato aggiornato per jQuery 1.8 qui: stackoverflow.com/a/2641047/850782
EpicVoyage

136

Se l'ordine è importante, è possibile creare i propri eventi e associare i callback al licenziamento quando tali eventi sono attivati ​​da altri callback.

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

Qualcosa del genere almeno.


23
e se volessimo interrompere la propagazione dell'evento click per i callback definiti prima del nostro codice di bind.
Vinilios,

2
Posso chiedere perché non è stato accettato? La risposta attualmente accettata cita la mia risposta come il metodo migliore, quindi sono un po 'confuso. :-)
dowski il

Questo funziona per il caso di mkoryak, ma non se lo stesso evento viene chiamato più volte. Ho un problema simile, in cui una singola sequenza di tasti attiva più volte $ (window) .scroll (), in ordine inverso .
Kxsong,

37

Il metodo di Dowski è buono se tutti i tuoi callback saranno sempre presenti e sei contento che siano dipendenti l'uno dall'altro.

Se si desidera che i callback siano indipendenti l'uno dall'altro, tuttavia, è possibile sfruttare il gorgogliamento e collegare eventi successivi come delegati agli elementi padre. I gestori su un elemento padre verranno attivati ​​dopo i gestori sull'elemento, proseguendo fino al documento. Questo è abbastanza buono come puoi usare event.stopPropagation(), event.preventDefault()ecc. Per saltare i gestori e annullare o annullare l'operazione.

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

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

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

Oppure, se non ti piace, puoi usare il plugin bindLast di Nick Leaches per forzare un evento a essere associato per ultimo: https://github.com/nickyleach/jQuery.bindLast .

Oppure, se stai usando jQuery 1.5, potresti anche potenzialmente fare qualcosa di intelligente con il nuovo oggetto differito.


6
.delegatenon è più usato e ora dovresti usare .on, ma non so come puoi raggiungere lo stesso comportamento conon
eric.itzhak,

il plugin deve ottenere fisso, dato che non funziona più vedere qui: stackoverflow.com/questions/12214654/...
Toskan

@ eric.itzhak Per fare in modo che .on () si comporti come il vecchio .delegate (), basta fornire un selettore. Dettagli qui api.jquery.com/delegate
Sam Kauffman

Se vuoi approfittare del gorgoglio, penso che l'evento debba essere diretto piuttosto che delegato . api.jquery.com/on/#direct-and-delegated-events
Ryne Everett

15

L'ordine in cui vengono richiamati i callback associati è gestito dai dati degli eventi di ciascun oggetto jQuery. Non ci sono funzioni (che io conosca) che ti consentano di visualizzare e manipolare direttamente quei dati, puoi usare solo bind () e unbind () (o una qualsiasi delle funzioni helper equivalenti).

Il metodo di Dowski è il migliore, è necessario modificare i vari callback associati per associare una sequenza ordinata di eventi personalizzati, con il "primo" callback associato all'evento "reale". In questo modo, indipendentemente dall'ordine in cui sono associati, la sequenza verrà eseguita nel modo giusto.

L'unica alternativa che posso vedere è qualcosa che davvero, davvero non vuoi contemplare: se sai che la sintassi vincolante delle funzioni potrebbe essere stata vincolata prima di te, prova a sbloccare tutte quelle funzioni e poi ricollegarle nell'ordine corretto te stesso. Questo è solo chiedere problemi, perché ora hai un codice duplicato.

Sarebbe bello se jQuery ti consentisse di cambiare semplicemente l'ordine degli eventi associati nei dati degli eventi di un oggetto, ma senza scrivere del codice da agganciare al core jQuery che non sembra possibile. E probabilmente ci sono implicazioni nel consentire ciò a cui non ho pensato, quindi forse è un'omissione intenzionale.


12
per vedere tutti gli eventi associati a un elemento da jquery usa var allEvents = $ .data (this, "events");
redsquare il

9

Si noti che nell'universo jQuery questo deve essere implementato diversamente dalla versione 1.8. La seguente nota di rilascio è dal blog jQuery:

.data ("eventi"): jQuery memorizza i suoi dati relativi agli eventi in un oggetto dati denominato (wait for it) eventi su ciascun elemento. Questa è una struttura di dati interna, quindi in 1.8 verrà rimossa dallo spazio dei nomi dei dati dell'utente in modo che non sia in conflitto con elementi con lo stesso nome. I dati degli eventi di jQuery sono ancora accessibili tramite jQuery._data (elemento, "eventi")

Abbiamo il controllo completo dell'ordine in cui i gestori eseguiranno nell'universo jQuery . Ricoo lo sottolinea sopra. Non sembra che la sua risposta gli abbia guadagnato molto amore, ma questa tecnica è molto utile. Si consideri, ad esempio, ogni volta che è necessario eseguire il proprio gestore prima di un gestore in un widget della libreria, oppure è necessario avere il potere di annullare la chiamata al gestore del widget in modo condizionale:

$("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

basta associare il gestore normalmente e quindi eseguire:

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

quindi ad esempio:

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

2
non funziona, ma $._data(element, 'events')[name].reverse()funziona
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

Puoi provare qualcosa del genere:

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

Penso che i "gestori dei proprietari" dovrebbero essere solo "gestori"
Joril,

1

Ho lo stesso problema e ho trovato questo argomento. le risposte di cui sopra possono risolvere questi problemi, ma non credo che siano buoni piani.

pensiamo al mondo reale.

se utilizziamo queste risposte, dobbiamo cambiare il nostro codice. devi cambiare il tuo stile di codice. qualcosa come questo:

originale:

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

incidere:

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

col passare del tempo, pensa al tuo progetto. il codice è brutto e difficile da leggere! anche se la ragione è semplice è sempre meglio. se hai 10 bindAtTheStart, potrebbero non esserci bug. se hai 100 bindAtTheStart, sei davvero sicuro di poterli tenere nel giusto ordine?

quindi se devi associare gli stessi eventi multipli. Penso che il modo migliore sia controllare l'ordine di caricamento js-file o js-code. jquery può gestire i dati degli eventi come coda. l'ordine è first-in, first-out. non è necessario modificare alcun codice. basta cambiare l'ordine di caricamento.


0

Ecco il mio colpo a questo, che copre diverse versioni di 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

In alcuni casi speciali, quando non è possibile modificare il modo in cui gli eventi click sono associati (i bind degli eventi sono fatti dai codici di altri) e si può cambiare l'elemento HTML, ecco una possibile soluzione ( avviso : questo non è il modo consigliato di associare eventi, altri sviluppatori potrebbero ucciderti per questo):

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

Con questo modo di associare, l'hander dell'evento verrà aggiunto per primo, quindi verrà eseguito per primo.


Questa non è una soluzione, è un suggerimento per una soluzione. Stai suggerendo che questo gestore "onclick" sta andando sull'elemento che ha già un gestore (jQuery) o che <span> sta avvolgendo l'elemento con il gestore jQuery?
Auspex,

-2

JQuery 1.5 introduce promesse ed ecco l'implementazione più semplice che ho visto per controllare l'ordine di esecuzione. Documentazione completa su http://api.jquery.com/jquery.when/

$.when( $('#myDiv').css('background-color', 'red') )
 .then( alert('hi!') )
 .then( myClickFunction( $('#myID') ) )
 .then( myThingToRunAfterClick() );
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.