Related
Lets say I have a web app which has a page that may contain 4 script blocks - the script I write may be found in one of those blocks, but I do not know which one, that is handled by the controller.
I bind some onclick events to a button, but I find that they sometimes execute in an order I did not expect.
Is there a way to ensure order, or how have you handled this problem in the past?
If order is important you can create your own events and bind callbacks to fire when those events are triggered by other callbacks.
$('#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;
});
Something like that at least.
Dowski's method is good if all of your callbacks are always going to be present and you are happy with them being dependant on each other.
If you want the callbacks to be independent of each other, though, you could be to take advantage of bubbling and attach subsequent events as delegates to parent elements. The handlers on a parent elements will be triggered after the handlers on the element, continuing right up to the document. This is quite good as you can use event.stopPropagation(), event.preventDefault(), etc to skip handlers and cancel or un-cancel the action.
$( '#mybutton' ).click( function(e) {
// Do stuff first
} );
$( '#mybutton' ).click( function(e) {
// Do other stuff first
} );
$( document ).delegate( '#mybutton', 'click', function(e) {
// Do stuff last
} );
Or, if you don't like this, you could use Nick Leaches bindLast plugin to force an event to be bound last: https://github.com/nickyleach/jQuery.bindLast.
Or, if you are using jQuery 1.5, you could also potentially do something clever with the new Deferred object.
I had been trying for ages to generalize this kind of process, but in my case I was only concerned with the order of first event listener in the chain.
If it's of any use, here is my jQuery plugin that binds an event listener that is always triggered before any others:
** UPDATED inline with jQuery changes (thanks 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);
Things to note:
This hasn't been fully tested.
It relies on the internals of the jQuery framework not changing (only tested with 1.5.2).
It will not necessarily get triggered before event listeners that are bound in any way other than as an attribute of the source element or using jQuery bind() and other associated functions.
The order the bound callbacks are called in is managed by each jQuery object's event data. There aren't any functions (that I know of) that allow you to view and manipulate that data directly, you can only use bind() and unbind() (or any of the equivalent helper functions).
Dowski's method is best, you should modify the various bound callbacks to bind to an ordered sequence of custom events, with the "first" callback bound to the "real" event. That way, no matter in what order they are bound, the sequence will execute in the right way.
The only alternative I can see is something you really, really don't want to contemplate: if you know the binding syntax of the functions may have been bound before you, attempt to un-bind all of those functions and then re-bind them in the proper order yourself. That's just asking for trouble, because now you have duplicated code.
It would be cool if jQuery allowed you to simply change the order of the bound events in an object's event data, but without writing some code to hook into the jQuery core that doesn't seem possible. And there are probably implications of allowing this that I haven't thought of, so maybe it's an intentional omission.
Please note that in the jQuery universe this must be implemented differently as of version 1.8. The following release note is from the jQuery blog:
.data(“events”): jQuery stores its event-related data in a data object
named (wait for it) events on each element. This is an internal data
structure so in 1.8 this will be removed from the user data name space
so it won’t conflict with items of the same name. jQuery’s event data
can still be accessed via jQuery._data(element, "events")
We do have complete control of the order in which the handlers will execute in the jQuery universe. Ricoo points this out above. Doesn't look like his answer earned him a lot of love, but this technique is very handy. Consider, for example, any time you need to execute your own handler prior to some handler in a library widget, or you need to have the power to cancel the call to the widget's handler conditionally:
$("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;
}
just bind handler normally and then run:
element.data('events').action.reverse();
so for example:
$('#mydiv').data('events').click.reverse();
You can try something like this:
/**
* 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]);
}
}
I have same issue and found this topic. the above answers can solve those problem, but I don't think them are good plans.
let us think about the real world.
if we use those answers, we have to change our code. you have to change your code style. something like this:
original:
$('form').submit(handle);
hack:
bindAtTheStart($('form'),'submit',handle);
as time goes on, think about your project. the code is ugly and hard to read! anthoer reason is simple is always better. if you have 10 bindAtTheStart, it may no bugs. if you have 100 bindAtTheStart, are you really sure you can keep them in right order?
so if you have to bind same events multiple.I think the best way is control js-file or js-code load order. jquery can handle event data as queue. the order is first-in, first-out. you don't need change any code. just change load order.
Here's my shot at this, covering different versions of 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;
}
});
}
});
In some special cases, when you cannot change how the click events are bound (event bindings are made from others' codes), and you can change the HTML element, here is a possible solution (warning: this is not the recommended way to bind events, other developers may murder you for this):
<span onclick="yourEventHandler(event)">Button</span>
With this way of binding, your event hander will be added first, so it will be executed first.
JQuery 1.5 introduces promises, and here's the simplest implementation I've seen to control order of execution. Full documentation at http://api.jquery.com/jquery.when/
$.when( $('#myDiv').css('background-color', 'red') )
.then( alert('hi!') )
.then( myClickFunction( $('#myID') ) )
.then( myThingToRunAfterClick() );
I'm looking for an answer if this is possible, or even a good idea.
Usually, as a fix for IE8 and earlier versions, a workaround similar to this is used:
var addEvent = function(obj, e, cb) {
if(obj.attachEvent) {
return obj.attachEvent('on' + e, cb);
} else {
obj.addEventListener(e, cb, false);
return true;
}
}
and then the programmer would always use addEvent instead of addEventListener.
Is there any way or point to create an addEventListener function that would wrap around the attachEvent when it is undefined?
I imagine it's a bit tricky since the EventTarget can be a DOM element, the document, or the window, or somethign else.
It depends on how far back you want to go. On IE8, you can extend Element.prototype to add features to all HTML elements, which is 90% at least of the work. I'm fairly sure you can't do that in IE6 (PrototypeJS had to resort to extending individual Element instances), I don't remember about IE7. Unless you're targeting east-Asian markets, you can basically ignore IE7 and earlier, though.
Here's an example of how you do that:
(function() {
// Functions for IE
function stopPropagation() {
this.cancelBubble = true;
}
function preventDefault() {
this.returnValue = false;
}
function addEventUsingAttach(eventName, handler)
{
this.attachEvent("on" + eventName, function() {
var e = window.event;
e.stopPropagation = stopPropagation;
e.preventDefault = preventDefault;
handler.call(this, e);
});
}
// Function to add `addEvent` to the given target object
function extendIt(target)
{
if (target.addEventListener) {
target.addEvent = Element.prototype.addEventListener;
}
else {
target.addEvent = addEventUsingAttach;
}
}
// Add it to `Element.prototype` if we have it
if (typeof Element !== "undefined" &&
typeof Element.prototype !== "undefined") {
extendIt(Element.prototype);
}
// Add it to `window` and `document` (etc.)
extendIt(window);
extendIt(document);
})();
Live Example | Source
Then you manually extend the other EventTargets, like window and document (see the end of the code listing above).
Original answer: I originally misread your question to be about adding addEventListener, specifically, on IE8, whereas in fact your question is quite clearly not about adding that, but adding your own addEvent. I'm leaving this answer in place for other readers:
It depends on how far back you want to go. On IE8, you can extend Element.prototype to add addEventListener to it, and that will be used by all HTML elements on the page (see below). The shim you add can't support the capturing phase, though, because IE didn't support it until they supported addEventListener natively. I'm fairly sure you can't extend Element.prototype on earlier versions (IE7, IE6), PrototypeJS had to fall back to extending specific elements for older versions of IE. But it works in IE8.
Having extended Element.prototype, then you'd have to manually extend the other event targets you mentioned, but extending Element.prototype does most of the work.
But if you do this and you include third-party scripts, beware that they may assume a correct implementation of addEventListeneer complete with the capturing phase.
Here's a basic shim for adding addEventListener to IE8:
(function() {
function stopPropagation() {
this.cancelBubble = true;
}
function preventDefault() {
this.returnValue = false;
}
if (typeof Element !== "undefined" &&
typeof Element.prototype !== "undefined" &&
!Element.prototype.addEventListener) {
Element.prototype.addEventListener = function(eventName, handler, useCapture) {
if (useCapture) {
throw "Browser doesn't support capturing phase";
}
this.attachEvent("on" + eventName, function() {
var e = window.event;
e.stopPropagation = stopPropagation;
e.preventDefault = preventDefault;
handler.call(this, e);
});
};
}
})();
Live Example | Source
Generally, functions can be added to an object by attaching the function to the object
obj.addEventListener = function(/* args*/) { /* body */ }
or by attaching the function to the prototype of the object constuctor
EventTarget.prototype.addEventListener = function(/* args*/) { /* body */ }
With the former, the added function can be called on the particular object only, while the latter lets you call the function on all objects that where or will be created from that particular object constuctor.
However,
EventTargets are host objects and therefore it is implementation dependant whether functions can be added to such objects.
EventTargets are host objects and therefore it is implementation dependant whether EventTarget.prototype actually exists and has the same meaning as with native objects.
If an object adheres to the EventTarget interface, it most likely already has an addEventListener function.
The event listener attached with attachEvent has different arguments than the one added with addEventListener.
In the end, it is not feasable to try and make a browser with crappy DOM support more standards complient using JavaScript.
I have a predicament: I want to send some data with an event listener but also be able to remove the listener. Here's the standard closure approach...
var fn = function(e){method(e,data)};
el.addEventListener('click',fn,false);
el.removeEventListener('click',fn,false);
and you could remove the event, just fine. But say, the element was removed from the DOM? Then, you'd be left with the fn function sitting around. After removing a couple thousand DOM elements, it will result in something of a memory leak.
I've considered attaching a DOMNodeRemoved event handler, that would remove any left over functions/data along with the removed node. But apparently, that event isn't cross-browser compatible.
The only other option I've come up with would be modifying the element's DOM. Consider...
el.MyEventData = function(e){method(e,data)};
el.addEventListener('click',el.MyEventData,false);
el.removeEventListener('click',el.MyEventData,false);
Is modifying the DOM acceptable in this situation? The only sticky part of that solution is when you try to add more than one event listener. Let's say we made a custom function to parse the adding/removing of events...
function makeEvent(fn,data){
var dataFn = function(e){fn(e,data)};
//create object to hold all added events
el.myEvents = {};
//make ID for this specific event
var eventID = ranString();
//add the event to the events object
el.myEvents[eventID] = [fn,dataFn];
//finally add the listener
el.addEventListener('click',dataFn,false);
}
function destroyEvent(fn){
//find all fn references
for(var id in el.myEvents){
if (el.myEvents[id][0] == fn){
el.removeEventListener('click',el.myEvents[id][1],false);
el.myEvents[id] = null;
}
}
}
It still modifies the DOM, as before, and certainly isn't a very elegant solution either. Does anyone know of any alternative, better method for passing data?
EDIT: So, I've looked into a little of jQuery's data/event scripts. I don't completely understand the code, so if someone would clarify, it would be helpful. But it seems as though they use a similar method, by making some type of el.cache property, that holds event data.
Considering that you use addEventListener this is not an issue as all modern garbage collectors can take care of such situations. The problem with event listeners only exists in IE's implementation (7-).
Test - 10 000 addEventListener and remove element (see Windows Task Manager)
When a DOM object contains a reference
to a JavaScript object (such an event
handling function), and when that
JavaScript object contains a reference
to that DOM object, then a cyclic
structure is formed. This is not in
itself a problem. At such time as
there are no other references to the
DOM object and the event handler, then
the garbage collector (an automatic
memory resource manager) will reclaim
them both, allowing their space to be
reallocated. The JavaScript garbage
collector understands about cycles and
is not confused by them.
http://www.crockford.com/javascript/memory/leak.html
Did you consider .delegate()?
According to your jQuery question:
Each jQ object has a data property. It does not stored inside the element itself - it's very important. jQ use general storage for all elements - jQuery.cache. So when you add anything to the element like this:
$('#myEl').data('someValue', 1);
jQ do the following:
jQuery.cache[elementUniqId]['someValue'] = 1;
So element does not contain its data object. It only have an uniq id that is allows it to access to the data recorde at the global storage. (elementUniqId is autogenerated)
jQ events are stored into the element data as well:
$('#myEl').click(function() { first listener });
$('#myEl').mouseenter(function() { one more listener });
$('#myEl').click(function() { anotheer listener });
Will be stored:
jQuery.cache[elementUniqId]['events'] = {
click: [function() { first listener }, function() { anotheer listene }],
mouseenter: [function() { one more listener }]
};
It allows jQ to store the order of execution for all listeners attached to each event. And later, when you delete dom element, using jQuery - .remove(), jQuery loops through the jQuery.cache[elementUniqId]['events'] and remove each listener from the element, and after removes element cache record. It allows jQ to preven memory leaks
A possible solution to maybe take you in a different direction: add the function as an inline sibling of the element.
<span id='element12345'>Test</span><script
type='text/javascript'>function fn12345() { /* ... */ }</script>
Then, when you remove all the event listeners that you want, you can also remove the "nextSibling()" of the element you're working with.
how about a setup like this? (using IE syntax since that's what I have available right now)
<div id="clickbox" style="width: 100px; height: 100px; border: 1px solid orange;">
click here to test</div>
<input id="Button1" type="button" value="clear handler" />
<script>
var data = "derp1";
var el = document.getElementById('clickbox');
var btn = document.getElementById('Button1');
// methods
var method = function (e, dat2) { alert(dat2); };
var fn = function (e) { method(e, data) };
var remover = null;
// attachment
el.attachEvent('onclick', fn, false);
(function (id, handler) {
// handler variable is local but points to the right function
remover = function (e) {
if (document.getElementById(id)) {
// remove the original listener (handler is fn)
document.getElementById(id).detachEvent('onclick', handler, false);
alert('detached');
}
// remove last reference to the original method
handler = null;
alert('method nulled');
// clean up the remover method
e.srcElement.detachEvent('onclick', remover);
remover = null;
};
btn.attachEvent('onclick', remover);
})('clickbox', fn);
// clear the original variable but the method still exists as an event listener
fn = null;
// you should be able to remove the div element and any references to it
// without leaving any stray bits around.
setTimeout( function() {
var d = document.getElementById('clickbox');
if (d){ d.parentNode.removeChild(d) ; }
} , 6000 );
el = null;
btn = null;
</script>
I'm assuming you don't want the listener removed immediately after adding it but rather want to be able to remove it at a later time. to deal with this, the cleanup routine is given its own scope by creating an anonymous function which is immediately invoked with fn as a parameter. the anon function then has its own reference to fn maintained in the handler variable. after that, fn can be cleaned up and the remaining references to the original method exist in the listener list for your element and in the scope of the anonymous function.
within the anonymous function scope, the function remover has access to the handler variable and can use it to detach the listener. remover then detaches and clears itself so there should be nothing left with access to any version of fn/handler.
I don't have any way to verify all this right now but I think it makes sense and should hold up in modern browsers.
why not take a look at this
Binding Events To Non-DOM Objects With jQuery
http://www.bennadel.com/blog/1520-Binding-Events-To-Non-DOM-Objects-With-jQuery.htm
The following doesn't work... (at least not in Firefox: document.getElementById('linkid').click() is not a function)
<script type="text/javascript">
function doOnClick() {
document.getElementById('linkid').click();
//Should alert('/testlocation');
}
</script>
<a id="linkid" href="/testlocation" onclick="alert(this.href);">Testlink</a>
You need to apply the event handler in the context of that element:
var elem = document.getElementById("linkid");
if (typeof elem.onclick == "function") {
elem.onclick.apply(elem);
}
Otherwise this would reference the context the above code is executed in.
The best way to solve this is to use Vanilla JS, but if you are already using jQuery, there´s a very easy solution:
<script type="text/javascript">
function doOnClick() {
$('#linkid').click();
}
</script>
<a id="linkid" href="/testlocation" onclick="alert(this.href);">Testlink</a>
Tested in IE8-10, Chrome, Firefox.
To trigger an event you basically just call the event handler for that
element. Slight change from your code.
var a = document.getElementById("element");
var evnt = a["onclick"];
if (typeof(evnt) == "function") {
evnt.call(a);
}
Granted, OP stated very similarly that this didn't work, but it did for me. Based on the notes in my source, it seems it was implemented around the time, or after, OP's post. Perhaps it's more standard now.
document.getElementsByName('MyElementsName')[0].click();
In my case, my button didn't have an ID. If your element has an id, preferably use the following (untested).
document.getElementById('MyElementsId').click();
I originally tried this method and it didn't work. After Googling I came back and realized my element was by name, and didn't have an ID. Double check you're calling the right attribute.
Source: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click
$("#linkid").trigger("click");
Old thread, but the question is still relevant, so...
(1) The example in your question now DOES work in Firefox. However in addition to calling the event handler (which displays an alert), it ALSO clicks on the link, causing navigation (once the alert is dismissed).
(2) To JUST call the event handler (without triggering navigation) merely replace:
document.getElementById('linkid').click();
with
document.getElementById('linkid').onclick();
Have a look at the handleEvent method
https://developer.mozilla.org/en-US/docs/Web/API/EventListener
"Raw" Javascript:
function MyObj() {
this.abc = "ABC";
}
MyObj.prototype.handleEvent = function(e) {
console.log("caught event: "+e.type);
console.log(this.abc);
}
var myObj = new MyObj();
document.querySelector("#myElement").addEventListener('click', myObj);
Now click on your element (with id "myElement") and it should print the following in the console:
caught event: click
ABC
This allows you to have an object method as event handler, and have access to all the object properties in that method.
You can't just pass a method of an object to addEventListener directly (like that: element.addEventListener('click',myObj.myMethod);) and expect myMethod to act as if I was normally called on the object. I am guessing that any function passed to addEventListener is somehow copied instead of being referenced. For example, if you pass an event listener function reference to addEventListener (in the form of a variable) then unset this reference, the event listener is still executed when events are caught.
Another (less elegant) workaround to pass a method as event listener and stil this and still have access to object properties within the event listener would be something like that:
// see above for definition of MyObj
var myObj = new MyObj();
document.querySelector("#myElement").addEventListener('click', myObj.handleEvent.bind(myObj));
If you're using this purely to reference the function in the onclick attribute, this seems like a very bad idea. Inline events are a bad idea in general.
I would suggest the following:
function addEvent(elm, evType, fn, useCapture) {
if (elm.addEventListener) {
elm.addEventListener(evType, fn, useCapture);
return true;
}
else if (elm.attachEvent) {
var r = elm.attachEvent('on' + evType, fn);
return r;
}
else {
elm['on' + evType] = fn;
}
}
handler = function(){
showHref(el);
}
showHref = function(el) {
alert(el.href);
}
var el = document.getElementById('linkid');
addEvent(el, 'click', handler);
If you want to call the same function from other javascript code, simulating a click to call the function is not the best way. Consider:
function doOnClick() {
showHref(document.getElementById('linkid'));
}
In general I would recommend against calling the event handlers 'manually'.
It's unclear what gets executed because of multiple registered
listeners
Danger to get into a recursive and infinite event-loop (click A
triggering Click B, triggering click A, etc.)
Redundant updates to the DOM
Hard to distinguish actual changes in the view caused by the user from changes made as initialisation code (which should be run only once).
Better is to figure out what exactly you want to have happen, put that in a function and call that manually AND register it as event listener.
Lets say I have a web app which has a page that may contain 4 script blocks - the script I write may be found in one of those blocks, but I do not know which one, that is handled by the controller.
I bind some onclick events to a button, but I find that they sometimes execute in an order I did not expect.
Is there a way to ensure order, or how have you handled this problem in the past?
If order is important you can create your own events and bind callbacks to fire when those events are triggered by other callbacks.
$('#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;
});
Something like that at least.
Dowski's method is good if all of your callbacks are always going to be present and you are happy with them being dependant on each other.
If you want the callbacks to be independent of each other, though, you could be to take advantage of bubbling and attach subsequent events as delegates to parent elements. The handlers on a parent elements will be triggered after the handlers on the element, continuing right up to the document. This is quite good as you can use event.stopPropagation(), event.preventDefault(), etc to skip handlers and cancel or un-cancel the action.
$( '#mybutton' ).click( function(e) {
// Do stuff first
} );
$( '#mybutton' ).click( function(e) {
// Do other stuff first
} );
$( document ).delegate( '#mybutton', 'click', function(e) {
// Do stuff last
} );
Or, if you don't like this, you could use Nick Leaches bindLast plugin to force an event to be bound last: https://github.com/nickyleach/jQuery.bindLast.
Or, if you are using jQuery 1.5, you could also potentially do something clever with the new Deferred object.
I had been trying for ages to generalize this kind of process, but in my case I was only concerned with the order of first event listener in the chain.
If it's of any use, here is my jQuery plugin that binds an event listener that is always triggered before any others:
** UPDATED inline with jQuery changes (thanks 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);
Things to note:
This hasn't been fully tested.
It relies on the internals of the jQuery framework not changing (only tested with 1.5.2).
It will not necessarily get triggered before event listeners that are bound in any way other than as an attribute of the source element or using jQuery bind() and other associated functions.
The order the bound callbacks are called in is managed by each jQuery object's event data. There aren't any functions (that I know of) that allow you to view and manipulate that data directly, you can only use bind() and unbind() (or any of the equivalent helper functions).
Dowski's method is best, you should modify the various bound callbacks to bind to an ordered sequence of custom events, with the "first" callback bound to the "real" event. That way, no matter in what order they are bound, the sequence will execute in the right way.
The only alternative I can see is something you really, really don't want to contemplate: if you know the binding syntax of the functions may have been bound before you, attempt to un-bind all of those functions and then re-bind them in the proper order yourself. That's just asking for trouble, because now you have duplicated code.
It would be cool if jQuery allowed you to simply change the order of the bound events in an object's event data, but without writing some code to hook into the jQuery core that doesn't seem possible. And there are probably implications of allowing this that I haven't thought of, so maybe it's an intentional omission.
Please note that in the jQuery universe this must be implemented differently as of version 1.8. The following release note is from the jQuery blog:
.data(“events”): jQuery stores its event-related data in a data object
named (wait for it) events on each element. This is an internal data
structure so in 1.8 this will be removed from the user data name space
so it won’t conflict with items of the same name. jQuery’s event data
can still be accessed via jQuery._data(element, "events")
We do have complete control of the order in which the handlers will execute in the jQuery universe. Ricoo points this out above. Doesn't look like his answer earned him a lot of love, but this technique is very handy. Consider, for example, any time you need to execute your own handler prior to some handler in a library widget, or you need to have the power to cancel the call to the widget's handler conditionally:
$("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;
}
just bind handler normally and then run:
element.data('events').action.reverse();
so for example:
$('#mydiv').data('events').click.reverse();
You can try something like this:
/**
* 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]);
}
}
I have same issue and found this topic. the above answers can solve those problem, but I don't think them are good plans.
let us think about the real world.
if we use those answers, we have to change our code. you have to change your code style. something like this:
original:
$('form').submit(handle);
hack:
bindAtTheStart($('form'),'submit',handle);
as time goes on, think about your project. the code is ugly and hard to read! anthoer reason is simple is always better. if you have 10 bindAtTheStart, it may no bugs. if you have 100 bindAtTheStart, are you really sure you can keep them in right order?
so if you have to bind same events multiple.I think the best way is control js-file or js-code load order. jquery can handle event data as queue. the order is first-in, first-out. you don't need change any code. just change load order.
Here's my shot at this, covering different versions of 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;
}
});
}
});
In some special cases, when you cannot change how the click events are bound (event bindings are made from others' codes), and you can change the HTML element, here is a possible solution (warning: this is not the recommended way to bind events, other developers may murder you for this):
<span onclick="yourEventHandler(event)">Button</span>
With this way of binding, your event hander will be added first, so it will be executed first.
JQuery 1.5 introduces promises, and here's the simplest implementation I've seen to control order of execution. Full documentation at http://api.jquery.com/jquery.when/
$.when( $('#myDiv').css('background-color', 'red') )
.then( alert('hi!') )
.then( myClickFunction( $('#myID') ) )
.then( myThingToRunAfterClick() );