I’ve been beating my head against the wall for days now trying to find the magic combination that will let me send dynamic parameters to my event handlers. Here is my polymorphic event handler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var addEvent = function ( obj, type, fn ) {
if ( obj.attachEvent ) {
addEvent = function ( obj, type, fn ) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
obj.attachEvent( 'on'+type, obj[type+fn] );
}
}
else {
addEvent = function ( obj, type, fn ) {
obj.addEventListener( type, fn, false );
}
}
}

Pretty standard stuff (the removeEvent handler is pretty much the same) where the first time it runs the function rewrites itself to only use the method required and not bother checking every time.

This is fine and dandy for attaching events like this with our without static arguments:

Example 1:

1
2
addEvent(myDiv[1],iDown,com.namespace.Project.myFunc1); // or
addEvent(myDiv[1],iUp,function(){com.namespace.Project.myFunc2(1,'no',3)});

However, if I wanted to pass some arguments into the event handler it doesn’t work:

Example 2:

1
2
3
4
addEvent(myDiv[a],iDown,com.namespace.Project.myFunc3(a,b,c));
addEvent(myDiv[b],iUp,function(){com.namespace.Project.myFunc4(a)};
addEvent(myDiv[c],iMove,(function(x){com.namespace.Project.myFunc5(x)}(a)));
addEvent(myDiv[d],iDown,(function(x){return function({com.namespace.Project.myFunc6(x)}})(b));

None of those will work! After trying every possible combination I could think of to send my event function argument into the addEvent() function with a dynamic variable (argument) of its own intact and maintaining the correct scope I gave up. I’m sure some javascript guru out there knows how to do it, but I’m only a casual programmer (actually I wouldn’t claim to be an actual programmer at all) so I don’t know enough to figure it out I guess.

That said, the following method works if you are attaching/adding the event directly without going through the addEvent() function with it:
Example 3:

1
2
3
4
5
if (document.attachEvent) {
myDiv[a].attachEvent('on'+iDown,(function(x){return function(){com.namespace.Project.myFunc((x-1),0)}}(a)));
} else {
myDiv[a].addEventListener(iDown,(function(x){return function(){com.namespace.Project.myFunc((x-1),0)}}(a)), false);
}

The variable “a” will be passed correctly into “com.namespace.Porject.myFunc()” using “x” to change the variable’s scope. If you don’t do it this way then “a” will be whatever its last value was in memory. For example if you were attaching these listeners inside a “for loop” where “a” advances from zero to ten, and you tried to just pass the value of “a” directly it would be set to “10” for every “myDiv” instead of 0,1,2,3…etc. as you would expect. But, as I said, I’m at a loss as to how (or even if) I can pass that function call into my addEvent() handler.

Another method you can use to pass arguments to the event is to add it inline to the DOM object:

Example 4:

1
myDiv[a].setAttribute('on'+iDown,'com.namespace.Project.myFunc('+(a-1)+',0)');

That literally writes it inline into the existing page object “myDiv[a]” but it isn’t a perfect solution either:

  1. setAttribute doesn’t work in IE7 and earlier
  2. it is inline, so it isn’t unobtrusive if that’s important to you.
  3. it can’t be detached/removed very easily (need a bunch of work traversing the DOM to do that)

The first problem can be somewhat solved by using “innerHTML” instead of “setAttribute” but it will replace any existing contents in “myDiv[a]” so you’d either want to construct your HTML with empty containers exclusively there to get content written into them, or use “createElement” for that purpose, then append it into the document. It’s actually fairly fast, but you need to be mindful of the differences between how browser may count nodes. In the end it will still have problems 2 and 3.

Lastly you can use an old-school method of using javascript and “document.write()” to literally write the HTML code into the page as it is loading. Depending on what code you use to do that you may still have all three of the aforementioned problems, and it is frankly about as obtrusive as javascript can get.

So, the answer to my question (for now at least) is to use the method in Example 3.

If you’re wondering what “iDown” and “iUp” is about I also have a simple check as follows:

1
2
3
4
5
6
7
8
9
10
11
if ('ontouchstart' in document.documentElement) {
var iDown = "touchstart";
var iUp = "touchend";
var iMove = "touchmove";
var iOver = "touchstart";
} else {
var iDown = "mousedown";
var iUp = "mouseup";
var iMove = "mousemove";
var iOver = "mouseover";
}

This is my handy shorthand method for whether my script is running on a touch interface device or one with a mouse. There are those who argue you should just use the mouse events since touch devices default to firing faux mouse events anyway, which is true, but doesn’t always yield the results you want (especially because a single touch fires multiple events, and even if they aren’t used the touch events still fire – there is about a 300 millisecond delay between the touch events firing and the default mouse events firing. Just long enough to cause an apparent lag in responsiveness in your interface). Also, some devices will ignore settings to prevent the default action, which can unintentionally fire an event twice (once for the touch and once for the mouse). This is bad if you have a button that toggles something because it will toggle on touch and immediately toggle again when the mouse event fires. Suffice it to say native touch events have their usefulness. Incidentally I ran into this on some Android devices where – despite preventDefault having been coded – it would still fire both touch and then the mouse events.

One simple “poor man’s preventDefault” way around the double firing is to create a variable that is normally set to zero, but attach an event listener to the document or body that sets the value to something else on the first touch event. Then, in each mouse event check the value of that variable in an if-else before actually running a function. If the value is zero have it run the function. If it is something else you know a touch event occurred and the mouse event shouldn’t run its code (even though the mouse event itself will still have fired).