Como solicitar eventos vinculados ao jQuery

164

Digamos que eu tenho um aplicativo da web que possui uma página que pode conter 4 blocos de script - o script que eu escrevo pode ser encontrado em um desses blocos, mas não sei qual deles é tratado pelo controlador.

Vinculo alguns onclickeventos a um botão, mas acho que eles às vezes são executados em uma ordem que eu não esperava.

Existe uma maneira de garantir a ordem ou como você lidou com esse problema no passado?

mkoryak
fonte
1
Adicione seus retornos de chamada a um objeto CallBack e, quando você quiser acioná-los, poderá acioná-los todos de uma vez e eles serão executados na ordem adicionada. api.jquery.com/jQuery.Callbacks
Tyddlywink

Respostas:

28

Há anos que eu tentava generalizar esse tipo de processo, mas no meu caso eu estava preocupado apenas com a ordem do primeiro ouvinte de eventos na cadeia.

Se for de alguma utilidade, aqui está o meu plugin jQuery que liga um ouvinte de eventos que é sempre acionado antes de qualquer outro:

** ATUALIZADO em linha com as alterações do jQuery (obrigado 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);

Coisas a serem observadas:

  • Isso não foi totalmente testado.
  • Ele conta com o interior da estrutura do jQuery que não muda (testado apenas com 1.5.2).
  • Ele não será necessariamente acionado antes dos ouvintes de eventos que são vinculados de alguma maneira, exceto como um atributo do elemento de origem ou usando o jQuery bind () e outras funções associadas.
Ed.
fonte
É importante observar que isso também funciona apenas para eventos que foram adicionados via jQuery.
Grinn
1
isso não funciona mais desde que é usado this.data("events"), veja aqui stackoverflow.com/questions/12214654/…
Toskan
4
Há uma função semelhante que foi atualizado para jQuery 1.8 aqui: stackoverflow.com/a/2641047/850782
EpicVoyage
136

Se a ordem for importante, você poderá criar seus próprios eventos e ligar os retornos de chamada quando esses eventos forem acionados por outros retornos de chamada.

$('#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 assim, pelo menos.

dowski
fonte
23
e se quisermos interromper a propagação do evento click para retornos de chamada definidos em algum momento antes do nosso código de ligação.
vinilios
2
Posso perguntar por que isso não foi aceito? A resposta atualmente aceita cita minha resposta como o melhor método, então estou meio confuso. :-)
dowski
Isso funciona no caso de mkoryak, mas não se o mesmo evento estiver sendo chamado várias vezes. Eu tenho um problema semelhante, em que um único pressionamento de tecla aciona $ (window) .scroll () várias vezes, na ordem inversa .
kxsong
37

O método de Dowski é bom se todos os seus retornos de chamada sempre estiverem presentes e você ficar feliz com eles dependerem um do outro.

Se você deseja que os retornos de chamada sejam independentes um do outro, você pode tirar vantagem da interferência e anexar eventos subsequentes como delegados aos elementos pai. Os manipuladores nos elementos pai serão acionados após os manipuladores no elemento, continuando até o documento. Isso é muito bom, como você pode usar event.stopPropagation(), event.preventDefault()etc, para pular manipuladores e cancelar ou cancelar a ação.

$( '#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, se você não gostar disso, poderá usar o plug-in bindLast de Nick Leaches para forçar a última vinculação de um evento: https://github.com/nickyleach/jQuery.bindLast .

Ou, se você estiver usando o jQuery 1.5, também poderá fazer algo inteligente com o novo objeto Adiado.

Daniel Lewis
fonte
6
.delegatenão é usado mais e agora você deve usar .on, mas eu não sei como você pode alcançar o mesmo comportamento comon
eric.itzhak
o plugin precisa ser corrigido, pois não funciona mais, veja aqui: stackoverflow.com/questions/12214654/…
Toskan
@ eric.itzhak Para fazer o .on () se comportar como o antigo .delegate (), forneça apenas um seletor. Detalhes aqui api.jquery.com/delegate
Sam Kauffman
Se você quiser tirar proveito da bolha, acho que o evento precisa ser direto, e não delegado . api.jquery.com/on/#direct-and-delegated-events
Ryne Everett
15

A ordem em que os retornos de chamada associados são chamados é gerenciada pelos dados de eventos de cada objeto jQuery. Não existem funções (que eu conheça) que permitam visualizar e manipular esses dados diretamente; você só pode usar bind () e unbind () (ou qualquer uma das funções auxiliares equivalentes).

O método de Dowski é o melhor, você deve modificar os vários retornos de chamada vinculados para vincular a uma sequência ordenada de eventos personalizados, com o "primeiro" retorno de chamada vinculado ao evento "real". Dessa forma, não importa em que ordem eles estejam vinculados, a sequência será executada da maneira correta.

A única alternativa que eu vejo é algo que você realmente não quer contemplar: se você sabe que a sintaxe de ligação das funções pode ter sido vinculada antes de você, tente desassociar todas essas funções e depois religá-las na ordem correta você mesmo. Isso está apenas pedindo problemas, porque agora você tem código duplicado.

Seria legal se o jQuery permitisse simplesmente alterar a ordem dos eventos vinculados nos dados de evento de um objeto, mas sem escrever algum código para ligar ao núcleo do jQuery que não parece possível. E provavelmente há implicações em permitir isso que eu não tenha pensado, então talvez seja uma omissão intencional.

Adam Bellaire
fonte
12
para ver todos os eventos vinculados a um elemento por jquery, use var allEvents = $ .data (this, "events");
redsquare
9

Observe que no universo jQuery isso deve ser implementado de maneira diferente da versão 1.8. A seguinte nota de versão é do blog do jQuery:

.data (“events”): o jQuery armazena seus dados relacionados a eventos em um objeto de dados chamado (aguarde) eventos em cada elemento. Essa é uma estrutura de dados interna; portanto, na versão 1.8, ela será removida do espaço de nome de dados do usuário para não entrar em conflito com itens com o mesmo nome. Os dados do evento do jQuery ainda podem ser acessados ​​via jQuery._data (elemento, "events")

Temos controle completo da ordem em que os manipuladores serão executados no universo jQuery . Ricoo aponta isso acima. Não parece que sua resposta lhe valeu muito amor, mas essa técnica é muito útil. Considere, por exemplo, sempre que você precisar executar seu próprio manipulador antes de algum manipulador em um widget de biblioteca ou precisar ter o poder de cancelar a chamada ao manipulador do 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();
});
cascavel da gaiola
fonte
7

basta ligar o manipulador normalmente e, em seguida, execute:

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

por exemplo:

$('#mydiv').data('events').click.reverse();
tomasbedrich
fonte
2
não funciona, mas $._data(element, 'events')[name].reverse()funciona
Attenzione 26/01
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
fonte
3

Você pode tentar algo como isto:

/**
  * 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]);
    }   
}
wildblue
fonte
Eu acho que "ownerhandlers" deve ser apenas "manipuladores"
Joril
1

Eu tenho o mesmo problema e encontrei este tópico. as respostas acima podem resolver esses problemas, mas não acho que sejam bons planos.

vamos pensar no mundo real.

se usarmos essas respostas, temos que mudar nosso código. você precisa alterar seu estilo de código. algo assim:

original:

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

hackear:

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

À medida que o tempo passa, pense no seu projeto. o código é feio e difícil de ler! anthoer razão é simples é sempre melhor. se você tiver 10 bindAtTheStart, pode haver erros. se você tiver 100 bindAtTheStart, tem certeza de que pode mantê-los na ordem certa?

Então, se você tiver que ligar vários mesmos eventos. Acho que a melhor maneira é controlar a ordem de carregamento do arquivo js ou do código js. jquery pode manipular dados de eventos como fila. o pedido é primeiro a entrar, primeiro a sair. você não precisa alterar nenhum código. basta alterar a ordem de carregamento.

9nix00
fonte
0

Aqui está minha chance, cobrindo diferentes versões do 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
fonte
0

Em alguns casos especiais, quando você não pode alterar como os eventos de clique são vinculados (as ligações de eventos são feitas a partir dos códigos de outras pessoas) e pode alterar o elemento HTML, aqui está uma solução possível ( aviso : esta não é a maneira recomendada de vincular eventos, outros desenvolvedores podem assassiná-lo por isso):

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

Com essa forma de associação, seu manipulador de eventos será adicionado primeiro e, portanto, será executado primeiro.

Barragem Linh
fonte
Isso não é uma solução, é uma dica de solução. Você está sugerindo que esse manipulador "onclick" esteja no elemento que já possui um manipulador (jQuery) ou seu <span> está agrupando o elemento com o manipulador jQuery?
Auspex #
-2

O JQuery 1.5 apresenta promessas, e aqui está a implementação mais simples que eu já vi para controlar a ordem de execução. Documentação completa em http://api.jquery.com/jquery.when/

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