Как заказать события, связанные с JQuery

164

Допустим, у меня есть веб-приложение, в котором есть страница, которая может содержать 4 блока сценариев - сценарий, который я пишу, может быть найден в одном из этих блоков, но я не знаю, какой из них обрабатывается контроллером.

Я связываю некоторые onclickсобытия с кнопкой, но обнаруживаю, что они иногда выполняются в порядке, которого я не ожидал.

Есть ли способ обеспечить порядок или как вы решали эту проблему в прошлом?

mkoryak
источник
1
Добавьте ваши обратные вызовы к объекту CallBack, и когда вы захотите запустить их, вы можете запустить их все сразу, и они будут выполняться в добавленном порядке. api.jquery.com/jQuery.Callbacks
Tyddlywink

Ответы:

28

Я годами пытался обобщить этот процесс, но в моем случае меня интересовал только порядок первого слушателя событий в цепочке.

Если он полезен, вот мой плагин jQuery, который связывает прослушиватель событий, который всегда запускается раньше других:

** ОБНОВЛЕНО в соответствии с изменениями jQuery (спасибо 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);

Что следует отметить:

  • Это не было полностью проверено.
  • Он полагается на то, что внутренняя часть фреймворка jQuery не меняется (тестируется только с 1.5.2).
  • Он не обязательно будет запущен перед слушателями событий, которые связаны каким-либо образом, кроме как в качестве атрибута исходного элемента или с помощью jQuery bind () и других связанных функций.
Ред.
источник
Важно отметить, что это также работает только для событий, которые были добавлены через jQuery.
Гринн
1
это больше не работает, так как он использует this.data("events"), см. здесь stackoverflow.com/questions/12214654/…
Toskan
4
Существует аналогичная функция, которая была обновлена ​​для jQuery 1.8 здесь: stackoverflow.com/a/2641047/850782
EpicVoyage
136

Если порядок важен, вы можете создавать свои собственные события и привязывать обратные вызовы к срабатыванию, когда эти события запускаются другими обратными вызовами.

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

Нечто подобное по крайней мере.

dowski
источник
23
что если мы хотим остановить распространение события click для обратных вызовов, определенных в некоторый момент перед нашим кодом связывания.
Винилиос
2
Могу я спросить, почему это не было принято? В настоящее время принятый ответ цитирует мой ответ как лучший метод, поэтому я немного растерялся. :-)
dowski
Это работает для случая mkoryak, но не в том случае, если одно и то же событие вызывается несколько раз. У меня есть похожая проблема, когда одно нажатие клавиши вызывает $ (window) .scroll () несколько раз в обратном порядке.
kxsong
37

Метод Доуски хорош, если все ваши обратные вызовы всегда будут присутствовать, и вы довольны тем, что они зависят друг от друга.

Однако если вы хотите, чтобы обратные вызовы были независимы друг от друга, вы можете воспользоваться всплывающими подсказками и прикрепить последующие события в качестве делегатов к родительским элементам. Обработчики родительских элементов будут запускаться после обработчиков элемента, продолжаясь вплоть до документа. Это очень хорошо, поскольку вы можете использовать event.stopPropagation()и event.preventDefault()т. Д., Чтобы пропустить обработчики и отменить или отменить действие.

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

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

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

Или, если вам это не нравится, вы можете использовать плагин Nick Leaches bindLast, чтобы событие было привязано последним: https://github.com/nickyleach/jQuery.bindLast .

Или, если вы используете jQuery 1.5, вы также можете сделать что-то умное с новым отложенным объектом.

Дэниел Льюис
источник
6
.delegateбольше не используется, и вы должны теперь использовать .on, но я не знаю, как вы можете достичь того же поведения сon
eric.itzhak
плагин должен быть исправлен, так как он больше не работает, см. здесь: stackoverflow.com/questions/12214654/…
Toskan
@ eric.itzhak Чтобы .on () вел себя как старый .delegate (), просто предоставьте ему селектор. Подробности здесь api.jquery.com/delegate
Сэм Кауфман
Если вы хотите воспользоваться пузырями, я думаю, что мероприятие должно быть прямым, а не делегированным . api.jquery.com/on/#direct-and-delegated-events
Райн Эверетт
15

Порядок вызова связанных обратных вызовов управляется данными событий каждого объекта jQuery. Нет никаких функций (о которых я знаю), которые бы позволяли вам просматривать и манипулировать этими данными напрямую, вы можете использовать только bind () и unbind () (или любую из эквивалентных вспомогательных функций).

Лучше всего использовать метод Доуски, вы должны модифицировать различные связанные обратные вызовы, чтобы связать их с упорядоченной последовательностью пользовательских событий, с «первым» обратным вызовом, связанным с «реальным» событием. Таким образом, независимо от того, в каком порядке они связаны, последовательность будет выполняться правильно.

Единственная альтернатива, которую я вижу, это то, что вы действительно, действительно, не хотите обдумывать: если вы знаете, что синтаксис привязки функций, возможно, был связан до вас, попытайтесь отменить привязку всех этих функций, а затем повторно связать их в правильном порядке самостоятельно. Это просто напрашивается на неприятности, потому что теперь у вас есть дублированный код.

Было бы здорово, если бы jQuery позволял вам просто изменять порядок связанных событий в данных событий объекта, но без написания некоторого кода для подключения к ядру jQuery, что не представляется возможным. И, вероятно, есть последствия того, что я это не подумал, поэтому, возможно, это намеренное упущение.

Адам Беллер
источник
12
чтобы увидеть все события, связанные с элементом с помощью jquery, используйте var allEvents = $ .data (this, "events");
красный квадрат
9

Обратите внимание, что во вселенной jQuery это должно быть реализовано по-другому, начиная с версии 1.8. Следующая заметка о выпуске взята из блога jQuery:

.data («events»): jQuery сохраняет свои связанные с событиями данные в объекте данных с именем (wait for it) events для каждого элемента. Это внутренняя структура данных, поэтому в 1.8 она будет удалена из пространства имен пользовательских данных, поэтому она не будет конфликтовать с элементами с одинаковыми именами. Доступ к данным о событиях в jQuery по-прежнему возможен через jQuery._data (элемент, "events")

У нас есть полный контроль над порядком выполнения обработчиков во вселенной jQuery . Рику указывает на это выше. Не похоже, что его ответ принес ему много любви, но эта техника очень удобна. Рассмотрим, например, всякий раз, когда вам нужно выполнить свой собственный обработчик до какого-либо обработчика в виджете библиотеки, или у вас должна быть возможность условно отменить вызов обработчика виджета:

$("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();
});
гремучая клетка
источник
8
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;
}
Робин
источник
7

просто связать обработчик нормально и затем запустить:

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

так например:

$('#mydiv').data('events').click.reverse();
tomasbedrich
источник
2
не работает, но это $._data(element, 'events')[name].reverse()работает
Attenzione
3

Вы можете попробовать что-то вроде этого:

/**
  * 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
источник
Я думаю, что «владельцы» должны быть просто «обработчиками»
Joril
1

У меня такая же проблема и нашел эту тему. Приведенные выше ответы могут решить эту проблему, но я не думаю, что это хорошие планы.

давайте подумаем о реальном мире.

если мы используем эти ответы, мы должны изменить наш код. Вы должны изменить свой стиль кода. что-то вроде этого:

оригинал:

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

взломать:

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

со временем подумай о своем проекте. код ужасен и труден для чтения! Другая причина проста, всегда лучше. если у вас есть 10 bindAtTheStart, возможно, нет ошибок. если у вас есть 100 bindAtTheStart, действительно ли вы уверены, что можете держать их в правильном порядке?

поэтому, если вам нужно связать несколько событий одинаково. Я думаю, что лучший способ - это управлять порядком загрузки js-файла или js-кода. JQuery может обрабатывать данные события как очередь. заказ первым пришел, первым вышел. вам не нужно менять код. просто измените порядок загрузки.

9nix00
источник
0

Вот мой пример, охватывающий различные версии 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
источник
0

В некоторых особых случаях, когда вы не можете изменить то, как связаны события клика (привязки событий сделаны из чужих кодов), и вы можете изменить элемент HTML, вот возможное решение ( предупреждение : это не рекомендуемый способ связывания события, другие разработчики могут убить вас за это):

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

При таком способе привязки ваш обработчик событий будет добавлен первым, поэтому он будет выполнен первым.

Линь Дам
источник
Это не решение, это намек на решение. Вы предлагаете, чтобы этот обработчик "onclick" работал с элементом, который уже имеет обработчик (jQuery), или ваш <span> оборачивает элемент обработчиком jQuery?
Auspex
-2

JQuery 1.5 вводит обещания, и вот самая простая реализация, которую я видел, для контроля порядка выполнения. Полная документация на http://api.jquery.com/jquery.when/

$.when( $('#myDiv').css('background-color', 'red') )
 .then( alert('hi!') )
 .then( myClickFunction( $('#myID') ) )
 .then( myThingToRunAfterClick() );
Таня Суини
источник