View Full Version : Add Multiple Event Handlers To HTML Element
itsallkizza
12-21-2008, 12:00 PM
Here's the better method (one everybody knows), but it will run the functions in first-in last-out order:
<script type="text/javascript">
// <![CDATA[
function addEventHandler(to_element,event,handler)
{
if (to_element.addEventListener) to_element.addEventListener(event,handler,false);
else if (to_element.attachEvent) to_element.attachEvent("on"+event,handler);
else return false;
}
// Example usage
function function1()
{
alert("first alert");
}
function function2()
{
alert("second alert");
}
addEventHandler(window,"load",function2);
addEventHandler(window,"load",function1);
// notice the reverse order
// ]]>
</script>
This one is a bit more complicated, but will run the functions in the same order they are given. I wish I didn't have to use eval(), but I couldn't think of a good way out of it (without using globals).
<script type="text/javascript">
// <![CDATA[
/*Author: itsallkizza*/
function addEventHandler(to_element,event,handler)
{
if (!to_element.event_lists) to_element.event_lists = new Object();
if (!to_element.event_lists[event])
{
to_element.event_lists[event] = new Array();
// I'd love to be able to do this without eval(), but I don't know a good way of linking the event with the event function array
eval('to_element["on"+event] = function(){for (var i=0;i<this.event_lists["'+event+'"].length;i++) this.event_lists["'+event+'"][i]()}');
}
to_element.event_lists[event].push(handler);
}
// Example usage
function function1()
{
alert("first alert");
}
function function2()
{
alert("second alert");
}
addEventHandler(window,"load",function1);
addEventHandler(window,"load",function2);
// notice it works in order
// ]]>
</script>
itsallkizza
12-26-2008, 06:02 AM
Little faster version of the first code, and the return false has been removed since this works in all current browsers (and presumably all future ones):
function addEventHandler(to_element,event,handler)
{
to_element.addEventListener ? to_element.addEventListener(event,handler,false) : to_element.attachEvent("on"+event,handler);
}
rnd me
12-26-2008, 07:01 AM
the posted version losses the meaning of "this" in IE.
here is a workaround that preserves "this".
the parameter interface is the same.
it also has a fallback for very old browsers.
event names can be specified with or without "on" in the string.
oh, and you can pass a function body as a string for the function, or use a function as is tradition.
function addEvent( obj, type, fn ) {
if(fn.charAt){ fn=Function(fn);}
var ename= type.replace(/^on/i,"");
var resp = obj.attachEvent ?
obj.attachEvent( "on" + ename, function(){ return fn.call(obj, window.event )} ) :
obj.addEventListener(ename, fn, false );
if(!resp){ obj["on"+ename] = fn; }
}
the main thing is the ie fix though.
itsallkizza
12-26-2008, 08:55 AM
Thanks :)
if(!resp){ obj["on"+ename] = fn; }
I had this in there originally, then decided to return false instead. I chose this because the whole idea of my original post was to add multiple events, and unfortunately if their browser doesn't support attachEvent or addEventListener, they cannot add more than one function to a single object.event (unless they use something like the code below).
How about this:
/*Author: itsallkizza,rnd me*/
function addEventHandler(to_element,event,handler)
{
if (typeof(handler)=="string") handler = Function(handler);
var f = event.substr(0,2);
var e = (f=="on"||f=="On"||f=="ON"||f=="oN") ? event.substr(2) : event;
var r = to_element.addEventListener ? to_element.addEventListener(event,handler,false) : to_element.attachEvent("on"+event,handler);
if (!r) addEventHandlerOrderMatters(to_element,e,handler);
}
function addEventHandlerOrderMatters(to_element,event,handler)
{
if (typeof(handler)=="string") handler = Function(handler);
var f = event.substr(0,2);
var e = (f=="on"||f=="On"||f=="ON"||f=="oN") ? event.substr(2) : event;
if (!to_element.event_lists) to_element.event_lists = new Object();
if (!to_element.event_lists[event])
{
to_element.event_lists[e] = new Array();
eval('to_element["on"+e] = function(){for (var i=0;i<this.event_lists["'+e+'"].length;i++) this.event_lists["'+e+'"][i]()}');
}
to_element.event_lists[e].push(handler);
}
Note: The first two lines actually speed tested faster than using a String.toLowerCase(), it even tested faster than your RegExp.replace() - man that object slows things down... lol. typeof(any_object_or_data_type)=="string" is also slightly faster than object detection (for the charAt).
In the above script, the user can jump right to using addEventHandlerOrderMatters if order matters to him. If order doesn't matter (or he wants it in reverse order) then he can use addEventHandler which will only resort to using addEventHandlerOrderMatters if their browser is so old it doesn't support attachEvent or addEventListener.
Can you explain this further:
function(){ return fn.call(obj, window.event )}
Since fn and obj aren't defined when the function() is called, how does this work?
Also, the above script works fine for "this" (in IE7 at least).
rnd me
12-26-2008, 10:23 AM
function(){ return fn.call(obj, window.event )}
Since fn and obj aren't defined when the function() is called, how does this work?
Also, the above script works fine for "this" (in IE7 at least).
functions control not just where code is evaluted, but when as well.
fn and obj will exist when the addEvent function is called.
fn is closed by this inner function;
the .call forces the event handler's this to refer to the object upon which it is bound. In IE, this refers to the element raising the event. these are not always the same thing...
secondly, passing window.event keeps you from having to code out the
usual "e=e||window.event" code in event handlers (though it wont hurt).
it makes the first argument, traditionally e,the same for ie as w3 browsers.
w3 browsers ignore the passed argument to pass an anon event object.
for IE, the event handler is fed window.event as arguments[0], like the w3 model.
basically it unifies the two event models a bit and makes it easier to code event handlers.
edit: hmmm. ill play with the code...
itsallkizza
12-26-2008, 10:27 AM
the .call forces the event handler's this to refer to the object upon which it is bound. In IE, this refers to the element raising the event. these are not always the same thing...- But what's being passed around is not the keyword this but rather a reference to the original object. That means that if the user passes this in as the to_element, it should work fine.
Give it a try, I ran a few tests calling adding handlers inside other handlers as well as using this to reference window - it all seemed to work fine. If I'm wrong, feel free to let me know ;)
rnd me
12-26-2008, 11:52 AM
here is a version that runs them in the same order as applied, no eval.
the "this" issue might have been fixed in 7, but it's a know issue for earlier ones.
calling it also lets us pass window.event, so that alone make it worthy in my book.
i also like the compactness of the argument conditioning, saves bandwidth.
even if it's a little slower, this isn't a function that's going to be called often enough to be noticeable.
function addEventHandler(elm, event, handler) {
handler = handler.charAt ? Function(handler) : handler;
elm = elm.charAt ? document.getElementById(elm) : elm;
var r=0, e=event.replace(/^on/i,"");
r = elm.attachEvent ?
elm.attachEvent("on" + e, function(){ return handler.call(elm, window.event )} ) :
elm.addEventListener(e, handler, false) ;
if (!r) { // old-school fallback
elm._ev = elm._ev || ({}) ;
elm._ev[e] = elm._ev[e] || ([]) ;
var stack = elm._ev[e]
stack.unshift(handler);
elm["on"+e] = function () {
for (var i = 0, mx = stack.length; i < mx; i++) {
var it = stack[i];
if (it.call(elm, window.event) === false){break;}
}
}
}
}
EDIT: (thanks for the head's up itsallkizza)
old events are run in reverse if the order applied, last in first out, like the two good event routines...
returning false interrupts the stack.
if you want to switch the order, you can re-arrange the for loop to count down...
itsallkizza
12-26-2008, 09:54 PM
old events are run in order appliedattachEvent and addEventListener are first in last out.
vwphillips
01-27-2009, 02:51 PM
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<script type="text/javascript">
/*<![CDATA[*/
function f1(ev,obj,par){
alert('f1');
alert(ev);
alert(obj==document);
alert(par);
}
function f2(ev,obj,par){
alert('f2');
alert(ev);
alert(obj==document);
alert(par);
}
function zxcEventsFIFO(obj,ev,fun,par){ /*idea by: itsallkizza*/
if (!obj.evtlist) obj.evtlist=[];
if (!obj.evtlist[ev]){
obj.evtlist[ev]=[];
obj['on'+ev]=function(){
for (var z0=0;z0<this.evtlist[ev].length;z0++){
var par=this.evtlist[ev][z0];
window[par[0]](par[1],this,par[2]);
}
}
}
obj.evtlist[ev].push([fun,ev,par]);
}
zxcEventsFIFO(document,'mousedown','f1',['a','b','c']);
zxcEventsFIFO(document,'mousedown','f2',['d','e','f']);
zxcEventsFIFO(document,'mousedown','f1',['a','b','c']);
zxcEventsFIFO(document,'mousedown','f2',['d','e','f']);
/*]]>*/
</script>
</head>
<body>
Just go round to looking at this.<br />
Main problem is that it overrides inline event calls.
</body>
</html>
vBulletin® v3.8.2, Copyright ©2000-2012, Jelsoft Enterprises Ltd.