Как заказать события, связанные с JQuery
Допустим, у меня есть веб-приложение, в котором есть страница, которая может содержать 4 блока сценария - сценарий, который я пишу, может быть найден в одном из этих блоков, но я не знаю, какой из них обрабатывается контроллером.
Я связываю некоторые onclick
события для кнопки, но я обнаружил, что они иногда выполняются в порядке, которого я не ожидал.
Есть ли способ обеспечить порядок или как вы решали эту проблему в прошлом?
12 ответов
Я целую вечность пытался обобщить этот процесс, но в моем случае меня интересовал только порядок первого слушателя событий в цепочке.
Если он полезен, вот мой плагин 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() и других связанных функций.
Если порядок важен, вы можете создавать свои собственные события и привязывать обратные вызовы к срабатыванию, когда эти события инициируются другими обратными вызовами.
$('#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;
});
Нечто подобное по крайней мере.
Метод Доуски хорош, если все ваши обратные вызовы всегда будут присутствовать, и вы довольны тем, что они зависят друг от друга.
Если вы хотите, чтобы обратные вызовы были независимы друг от друга, вы могли бы использовать всплывающие подсказки и присоединять последующие события в качестве делегатов к родительским элементам. Обработчики родительских элементов будут запускаться после обработчиков элемента, продолжая вплоть до документа. Это очень хорошо, так как вы можете использовать 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, вы также можете сделать что-то умное с новым отложенным объектом.
Порядок вызова связанных обратных вызовов управляется данными события каждого объекта jQuery. Нет каких-либо функций (которые я знаю), которые бы позволяли вам просматривать и манипулировать этими данными напрямую, вы можете использовать только bind() и unbind() (или любую из эквивалентных вспомогательных функций).
Лучше всего использовать метод Доуски, вы должны модифицировать различные связанные обратные вызовы, чтобы связать их с упорядоченной последовательностью пользовательских событий, с "первым" обратным вызовом, связанным с "реальным" событием. Таким образом, независимо от того, в каком порядке они связаны, последовательность будет выполняться правильно.
Единственная альтернатива, которую я вижу, это то, что вы действительно, действительно, не хотите обдумывать: если вы знаете, что синтаксис привязки функций, возможно, был связан до вас, попытайтесь отменить привязку всех этих функций, а затем повторно связать их в правильном порядке самостоятельно. Это просто напрашивается на неприятности, потому что теперь у вас есть дублированный код.
Было бы здорово, если бы jQuery позволял вам просто изменять порядок связанных событий в данных событий объекта, но без написания некоторого кода для подключения к ядру jQuery, что не представляется возможным. И, вероятно, есть последствия того, что я это не подумал, так что, возможно, это намеренное упущение.
Обратите внимание, что во вселенной 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();
});
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;
}
Просто связать обработчик нормально и затем запустить:
element.data('events').action.reverse();
так например:
$('#mydiv').data('events').click.reverse();
Вы можете попробовать что-то вроде этого:
/**
* 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]);
}
}
У меня такая же проблема и нашел эту тему. Приведенные выше ответы могут решить эту проблему, но я не думаю, что это хорошие планы.
давайте подумаем о реальном мире.
если мы используем эти ответы, мы должны изменить наш код. Вы должны изменить свой стиль кода. что-то вроде этого:
оригинал:
$('form').submit(handle);
взломать:
bindAtTheStart($('form'),'submit',handle);
со временем подумайте о своем проекте. код ужасен и труден для чтения! Другая причина проста, всегда лучше. если у вас есть 10 bindAtTheStart, возможно, нет ошибок. если у вас есть 100 bindAtTheStart, действительно ли вы уверены, что можете держать их в правильном порядке?
поэтому, если вам нужно связать несколько событий одинаково. Я думаю, что лучший способ - это управлять порядком загрузки js-файла или js-кода. JQuery может обрабатывать данные события как очередь. заказ первым пришел, первым вышел. вам не нужно менять код. просто измените порядок загрузки.
Вот мой пример, охватывающий различные версии 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;
}
});
}
});
В некоторых особых случаях, когда вы не можете изменить то, как связаны события клика (привязки событий сделаны из чужих кодов), и вы можете изменить элемент HTML, вот возможное решение (предупреждение: это не рекомендуемый способ связывания события, другие разработчики могут убить вас за это):
<span onclick="yourEventHandler(event)">Button</span>
При таком способе привязки ваш обработчик событий будет добавлен первым, поэтому он будет выполнен первым.
JQuery 1.5 вводит обещания, и вот самая простая реализация, которую я когда-либо видел, чтобы контролировать порядок выполнения. Полная документация на http://api.jquery.com/jquery.when/
$.when( $('#myDiv').css('background-color', 'red') )
.then( alert('hi!') )
.then( myClickFunction( $('#myID') ) )
.then( myThingToRunAfterClick() );