Cómo ordenar eventos vinculados con jQuery

164

Digamos que tengo una aplicación web que tiene una página que puede contener 4 bloques de script: el script que escribo se puede encontrar en uno de esos bloques, pero no sé cuál es el controlador.

Ato algunos onclickeventos a un botón, pero encuentro que a veces se ejecutan en un orden que no esperaba.

¿Hay alguna manera de garantizar el orden o cómo ha manejado este problema en el pasado?

mkoryak
fuente
1
Agregue sus devoluciones de llamada a un objeto CallBack y cuando desee dispararlas, puede dispararlas todas a la vez y se ejecutarán en el orden agregado. api.jquery.com/jQuery.Callbacks
Tyddlywink

Respuestas:

28

Había intentado durante años generalizar este tipo de proceso, pero en mi caso solo me preocupaba el orden del primer oyente de eventos en la cadena.

Si es de alguna utilidad, aquí está mi complemento jQuery que une un detector de eventos que siempre se activa antes que cualquier otro:

** ACTUALIZADO en línea con los cambios de jQuery (gracias 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);

Cosas a tener en cuenta:

  • Esto no ha sido completamente probado.
  • Se basa en que las partes internas del marco jQuery no cambian (solo probado con 1.5.2).
  • No necesariamente se activará antes de los oyentes de eventos que están vinculados de cualquier otra manera que no sea como un atributo del elemento fuente o usando jQuery bind () y otras funciones asociadas.
Ed.
fuente
Es importante tener en cuenta que esto también solo funciona para eventos que se agregaron a través de jQuery.
Grinn
1
esto ya no funciona ya que usa this.data("events"), vea aquí stackoverflow.com/questions/12214654/…
Toskan
44
Hay una función similar que se ha actualizado para jQuery 1.8 aquí: stackoverflow.com/a/2641047/850782
EpicVoyage
136

Si el orden es importante, puede crear sus propios eventos y vincular devoluciones de llamada para que se activen cuando esos eventos se activen por otras devoluciones de llamada.

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

Algo así al menos.

Dowski
fuente
23
¿Qué pasa si queremos detener la propagación del evento click para devoluciones de llamada definidas en algún momento antes de nuestro código de enlace?
vinilios
2
¿Puedo preguntar por qué esto no fue aceptado? La respuesta actualmente aceptada cita mi respuesta como el mejor método, por lo que estoy un poco confundido. :-)
dowski
Esto funciona para el caso de mkoryak, pero no si el mismo evento se llama varias veces. Tengo un problema similar, donde una sola pulsación de tecla activa $ (window) .scroll () varias veces, en orden inverso .
kxsong
37

El método de Dowski es bueno si todas tus devoluciones de llamada siempre estarán presentes y estás contento de que dependan unas de otras.

Sin embargo, si desea que las devoluciones de llamada sean independientes entre sí, podría aprovechar el burbujeo y adjuntar eventos posteriores como delegados a elementos primarios. Los manejadores en un elemento primario se activarán después de los manejadores en el elemento, continuando hasta el documento. Esto es bastante bueno, ya que puede usar event.stopPropagation(), event.preventDefault()etc. para omitir controladores y cancelar o cancelar la acción.

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

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

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

O, si no le gusta esto, puede usar el complemento bindLast de Nick Leaches para forzar que un evento se vincule en último lugar: https://github.com/nickyleach/jQuery.bindLast .

O, si está utilizando jQuery 1.5, también podría hacer algo inteligente con el nuevo objeto diferido.

Daniel Lewis
fuente
66
.delegateya no se usa y ahora debería usarlo .on, sin embargo, no sé cómo puede alcanzar el mismo comportamiento conon
eric.itzhak
el complemento debe
repararse
@ eric.itzhak Para hacer que .on () se comporte como el antiguo .delegate (), simplemente proporcione un selector. Detalles aquí api.jquery.com/delegate
Sam Kauffman
Si desea aprovechar el burbujeo, creo que el evento debe ser directo en lugar de delegado . api.jquery.com/on/#direct-and-delegated-events
Ryne Everett
15

El orden en que se invocan las devoluciones de llamada enlazadas se gestiona mediante los datos de eventos de cada objeto jQuery. No hay ninguna función (que yo sepa) que le permita ver y manipular esos datos directamente, solo puede usar bind () y unbind () (o cualquiera de las funciones auxiliares equivalentes).

El método de Dowski es el mejor, debe modificar las varias devoluciones de llamada enlazadas para enlazar a una secuencia ordenada de eventos personalizados, con la "primera" devolución de llamada vinculada al evento "real". De esa manera, no importa en qué orden estén vinculados, la secuencia se ejecutará de la manera correcta.

La única alternativa que puedo ver es algo que realmente no desea contemplar: si sabe que la sintaxis de enlace de las funciones puede haberse vinculado antes que usted, intente deshacer todas esas funciones y luego volver a vincularlas en el orden correcto usted mismo. Eso es solo pedir problemas, porque ahora tienes un código duplicado.

Sería genial si jQuery le permitiera simplemente cambiar el orden de los eventos enlazados en los datos de eventos de un objeto, pero sin escribir algún código para engancharlo al núcleo de jQuery que no parece posible. Y probablemente haya implicaciones de permitir esto en lo que no he pensado, así que tal vez sea una omisión intencional.

Adam Bellaire
fuente
12
para ver todos los eventos vinculados a un elemento por jquery, use var allEvents = $ .data (esto, "eventos");
Redsquare
9

Tenga en cuenta que en el universo jQuery esto debe implementarse de manera diferente a partir de la versión 1.8. La siguiente nota de lanzamiento es del blog jQuery:

.data ("eventos"): jQuery almacena sus datos relacionados con eventos en un objeto de datos llamado (espere) eventos en cada elemento. Esta es una estructura de datos interna, por lo que en 1.8 se eliminará del espacio de nombre de datos de usuario para que no entre en conflicto con elementos del mismo nombre. Aún se puede acceder a los datos de eventos de jQuery a través de jQuery._data (elemento, "eventos")

Tenemos control completo del orden en que los manejadores se ejecutarán en el universo jQuery . Ricoo señala esto arriba. No parece que su respuesta le haya ganado mucho amor, pero esta técnica es muy útil. Considere, por ejemplo, cada vez que necesite ejecutar su propio controlador antes de algún controlador en un widget de biblioteca, o necesite tener el poder de cancelar la llamada al controlador del widget condicionalmente:

$("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();
});
sonajero de jaula
fuente
7

simplemente vincula el controlador normalmente y luego ejecuta:

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

así por ejemplo:

$('#mydiv').data('events').click.reverse();
tomasbedrich
fuente
2
no funciona, pero esto $._data(element, 'events')[name].reverse()funciona
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;
}
Robin
fuente
3

Puedes probar algo como esto:

/**
  * 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]);
    }   
}
azul salvaje
fuente
Creo que los "manejadores de propietarios" deberían ser solo "manejadores"
Joril
1

Tengo el mismo problema y encontré este tema. Las respuestas anteriores pueden resolver esos problemas, pero no creo que sean buenos planes.

pensemos en el mundo real.

Si usamos esas respuestas, tenemos que cambiar nuestro código. Tienes que cambiar tu estilo de código. algo como esto:

original:

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

cortar a tajos:

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

A medida que pasa el tiempo, piense en su proyecto. ¡el código es feo y difícil de leer! La razón de antojo es simple, siempre es mejor. si tiene 10 bindAtTheStart, es posible que no haya errores. si tiene 100 bindAtTheStart, ¿está realmente seguro de que puede mantenerlos en el orden correcto?

así que si tiene que vincular los mismos eventos múltiples. Creo que la mejor manera es controlar el orden js-file o js-code. jquery puede manejar datos de eventos como cola. el orden es primero en entrar, primero en salir. No necesitas cambiar ningún código. solo cambia el orden de carga.

9nix00
fuente
0

Aquí está mi oportunidad, cubriendo diferentes versiones 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
fuente
0

En algunos casos especiales, cuando no puede cambiar la forma en que se enlazan los eventos de clic (los enlaces de eventos se hacen a partir de los códigos de otros), y puede cambiar el elemento HTML, aquí hay una posible solución ( advertencia : esta no es la forma recomendada para enlazar eventos, otros desarrolladores pueden asesinarte por esto):

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

Con esta forma de vinculación, su gestor de eventos se agregará primero, por lo que se ejecutará primero.

Presa Linh
fuente
Eso no es una solución, es una pista de una solución. ¿Está sugiriendo que este controlador "onclick" va en el elemento que ya tiene un controlador (jQuery), o su <span> está envolviendo el elemento con el controlador jQuery?
Auspex
-2

JQuery 1.5 presenta promesas, y aquí está la implementación más simple que he visto para controlar el orden de ejecución. Documentación completa en http://api.jquery.com/jquery.when/

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