Присоединение обработчиков событий jQuery, чтобы они запускались сначала
Есть ли способ связать обработчик событий jQuery таким образом, чтобы обработчик запускался перед любыми ранее прикрепленными обработчиками событий? Я наткнулся на эту статью, но код не работал, потому что обработчики событий больше не хранятся в массиве, что и ожидал его код, Я попытался создать расширение jQuery, чтобы делать то, что хотел, но это не работает (события все еще срабатывают в том порядке, в котором они были связаны):
$.fn.extend({
bindFirst: function(type, handler) {
var baseType = type;
var dotIdx = type.indexOf('.');
if (dotIdx >= 0) {
baseType = type.substr(0, dotIdx);
}
this.each(function() {
var oldEvts = {};
var data = $.data(this);
var events = data.events || data.__events__;
var handlers = events[baseType];
for (var h in handlers) {
if (handlers.hasOwnProperty(h)) {
oldEvts[h] = handlers[h];
delete handlers[h];
// Also tried an unbind here, to no avail
}
}
var self = $(this);
self.bind(type, handler);
for (var h in oldEvts) {
if (oldEvts.hasOwnProperty(h)) {
self.bind(baseType, oldEvts[h]);
}
}
});
}
});
Есть ли естественный способ изменить порядок обработки событий? Если нет, знаете ли вы, какую технику я мог бы применить? Я использую jQuery 1.4.1, хотя я обновляюсь, если должен.
Ответы
Ответ 1
Вот простой плагин, который я сделал некоторое время назад. Позволяет привязать обработчик к началу списка. Это очень просто, и я не гарантирую, что он будет работать с событиями с именами или что-то ужасное.
Для просто связывания отдельной или отдельной отдельной группы событий она должна работать.
Пример: http://jsfiddle.net/gbcUy/
$.fn.bindUp = function(type, fn) {
type = type.split(/\s+/);
this.each(function() {
var len = type.length;
while( len-- ) {
$(this).bind(type[len], fn);
var evt = $.data(this, 'events')[type[len]];
evt.splice(0, 0, evt.pop());
}
});
};
Или, если вы хотите каким-то другим образом манипулировать массивом обработчиков, просто получите обработчики для элемента, который вы хотите, и манипулируйте им, но вы хотите:
Пример: http://jsfiddle.net/gbcUy/1/
var clickHandlers = $('img').data('events').click;
clickHandlers.reverse(); // reverse the order of the Array
Ответ 2
Существует довольно приятный плагин под названием jQuery.bind-first, который предоставляет аналоги натурных on
, bind
, delegate
и live
методы, которые вызывают событие в верхней части очереди регистрации. Он также учитывает различия в регистрации событий между версиями от 1.7 до более ранних версий. Здесь как его использовать:
$('button')
.on ('click', function() { /* Runs second */ })
.onFirst('click', function() { /* Runs first */ });
Как и в большинстве этих ответов, большой недостаток заключается в том, что он опирается на внутреннюю логику регистрации событий jQuery и может легко сломаться, если он изменится, как это было в версии 1.7! Возможно, было бы лучше для долговечности вашего проекта найти решение, которое не связано с захватом внутренних компонентов jQuery.
В моем конкретном случае я пытался заставить два плагина играть хорошо. Я обработал его с помощью пользовательских событий, как описано в документации для метода trigger
. Возможно, вы сможете адаптировать подобный подход к своим обстоятельствам. Вот пример, чтобы вы начали:
$('button')
.on('click', function() {
// Declare and trigger a "before-click" event.
$(this).trigger('before-click');
// Subsequent code run after the "before-click" events.
})
.on('before-click', function() {
// Run before the main body of the click event.
});
И, в случае необходимости, здесь, как установить свойства объекта события, переданные функции обработчика, и получить результат последнего события before-click
для выполнения:
// Add the click event pageX and pageY to the before-click event properties.
var beforeClickEvent = $.Event('before-click', { pageX: e.pageX, pageY: e.pageY });
$(this).trigger(beforeClickEvent);
// beforeClickEvent.result holds the return value of the last before-click event.
if (beforeClickEvent.result === 'no-click') return;
Ответ 3
@patrick: Я пытался решить ту же проблему, и это решение делает именно то, что мне нужно. Одна из незначительных проблем заключается в том, что ваш плагин не обрабатывает пространство имен для нового события. Эта небольшая настройка должна позаботиться об этом:
Изменить:
var evt = $.data(this, 'events')[type[len]];
в
var evt = $.data(this, 'events')[type[len].replace(/\..+$/, '')];
Ответ 4
как насчет этого?
связать событие и сделать это:
handlers.unshift( handlers.pop() );
Ответ 5
В дополнение к выбранному ответу, рассмотрите недостающие параметры:
jQuery.fn.bindUp = function (type, parameters, fn) {
type = type.split(/\s+/);
this.each(function () {
var len = type.length;
while (len--) {
if (typeof parameters === "function")
jQuery(this).bind(type[len], parameters);
else
jQuery(this).bind(type[len], parameters, fn);
var evt = jQuery._data(this, 'events')[type[len]];
evt.splice(0, 0, evt.pop());
}
});
};
Ответ 6
Как указано здесь fooobar.com/questions/31134/..., вы можете сделать вот так:
<span onclick="yourEventHandler(event)">Button</span>
Предупреждение: это не рекомендуемый способ привязки событий, другие разработчики могут вас убить.
Ответ 7
Здесь используется комбинация некоторых предыдущих методов, включая поддержку обработчиков, пространств имен, привязок без jquery и поддержки:
$.fn.oneFirst = function(event_type, event_callback, handler) {
return this.bindFirst(event_type, event_callback, handler, "one");
},
$.fn.bindFirst = function(event_type, event_callback, handler, bind_type) {
var event_types = event_type.split(/\s+/);
var pos;
handler = (handler == undefined ? event_callback : handler);
event_callback = (typeof event_callback == "function" ? {} : event_callback);
this.each(function() {
var $this = $(this);
for (var i in event_types) { // each bound type
event_type = event_types[i];
var event_namespace = ((pos = event_type.indexOf(".")) > 0 ? event_type.substring(pos) : "");
event_type = (pos > 0 ? event_type.substring(0, pos) : event_type);
var current_attr_listener = this["on" + event_type];
if (current_attr_listener) { // support non-jquery binded events
$this.bind(event_type, function(e) {
return current_attr_listener(e.originalEvent);
});
this["on" + event_type] = null;
}
if (bind_type == "one") {
$this.one(event_type + event_namespace, event_callback, handler);
}
else {
$this.bind(event_type + event_namespace, event_callback, handler);
}
var all_events = $.data(this, 'events') || $._data(this).events;
var type_events = all_events[event_type];
var new_event = type_events.pop();
type_events.unshift(new_event);
}
});
};
Ответ 8
Моя лучшая попытка
У меня был код, который был структурирован следующим образом:
var $body = jQuery('body');
$body.on({
'click': function(event){
}
});
Чтобы затем убедиться, что обратный вызов был первым, я использовал эту функцию:
/**
* promoteLastEvent
*
* @access public
* @param jQuery $element
* @param String eventName
* @return void
*/
function promoteLastEvent($element, eventName) {
var events = jQuery._data($element.get(0), 'events'),
eventNameEvents = events[eventName],
lastEvent = eventNameEvents.pop();
eventNameEvents.splice(1, 0, lastEvent);
};
Это называется следующим образом:
promoteLastEvent($body, 'click');
Это работает довольно хорошо для меня, учитывая мое ограниченное использование $.fn.on
.
Ответ 9
Я создаю очень простую функцию, чтобы поместить свой обработчик события нажатия на первую позицию, и все существующие обработчики нажатия будут запускаться только вручную.
$.fn.bindClickFirst = function (eventHandler) {
var events = $._data($next[0], "events");
var clickEvtHandlers = [];
events.click.forEach(function (evt) {
clickEvtHandlers.push(evt.handler);
});
$next.off("click");
$next.on("click", function () {
var evtArg = event;
eventHandler(evtArg, function () {
clickEvtHandlers.forEach(function (evt) {
evt(evtArg);
});
});
})
}
И использовать функцию:
$btn.bindClickFirst(function (evt, next) {
setTimeout(function () {
console.log("bbb");
next();
}, 200)
})