Attaching jQuery event handlers so that they are triggered first
Is there a way to attach a jQuery event handler such that the handler is triggered before any previously-attached event handlers? I came across this article, but the code didn't work because event handlers are no-longer stored in an array, which is what his code expected. I attempted to create a jQuery extension to do what I wanted, but this is not working (the events still fire in the order they were bound):
$.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]);
}
}
});
}
});
Is there a natural way to reorder event handling? If there isn't, do you know of technique I could apply? I'm using jQuery 1.4.1, though I'll upgrade if I must.
Here's a simple plugin I did a while back. Lets you bind a handler to the beginning of the list. It is very simple, and I wouldn't guarantee that it works with namespaced events or anything terribly fancy.
For simply binding a single or space separate group of events, it should work.
Example: 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());
}
});
};
Or if you wanted to manipulate the Array of handlers in some other manner, just get the handlers for the element you want, and manipulate it however you want:
Example: http://jsfiddle.net/gbcUy/1/
var clickHandlers = $('img').data('events').click;
clickHandlers.reverse(); // reverse the order of the Array
There is a rather nice plugin called jQuery.bind-first that provides analogues of the native on
, bind
, delegate
and live
methods which push an event to the top of the registration queue. It also takes account of differences in event registration between 1.7 and earlier versions. Here's how to use it:
$('button')
.on ('click', function() { /* Runs second */ })
.onFirst('click', function() { /* Runs first */ });
As with most of these answers, the big disadvantage is that it relies on jQuery's internal event registration logic and could easily break if it changes—like it did in version 1.7! It might be better for the longevity of your project to find a solution that doesn't involve hijacking jQuery internals.
In my particular case, I was trying to get two plugins to play nice. I handled it using custom events as described in the documentation for the trigger
method. You may be able to adapt a similar approach to your own circumstances. Here's an example to get you started:
$('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.
});
And, in case you need to, here's how to set properties on the event object passed to the handler function and access the result of the last before-click
event to execute:
// Add the click event's 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;
As answered here https://stackoverflow.com/a/35472362/1815779, you can do like this:
<span onclick="yourEventHandler(event)">Button</span>
Warning: this is not the recommended way to bind events, other developers may murder you for this.
@patrick: I've been trying to solve the same problem and this solution does exactly what I need. One minor problem is that your plug-in doesn't handle namespacing for the new event. This minor tweak should take care of it:
Change:
var evt = $.data(this, 'events')[type[len]];
to:
var evt = $.data(this, 'events')[type[len].replace(/\..+$/, '')];
what about this? bind the event and than do this:
handlers.unshift( handlers.pop() );
Here's a combination of some prior methods including support for handlers, namespacing, non-jquery bindings, and once support:
$.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);
}
});
};
In addition to the selected answer, consider it's missing parameters:
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());
}
});
};
My best attempt.
I had code that was structured as follows:
var $body = jQuery('body');
$body.on({
'click': function(event){
}
});
To then ensure that the callback was the first one called, I used this function:
/**
* 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);
};
This is called as follows:
promoteLastEvent($body, 'click');
It works quite well for me, given my limited use of $.fn.on
.
I create a very simple function to put my click event handler at first position, and all existing click handlers will only be triggered manually.
$.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);
});
});
})
}
And to use the function:
$btn.bindClickFirst(function (evt, next) {
setTimeout(function () {
console.log("bbb");
next();
}, 200)
})
精彩评论