The following Javascript functions may or may not be useful to you. For me, they make me not hate working in Javascript. I find they work well in conjunction with the DOM code generator in Aardvark and its createElement() function, but neither are dependent on one another.
There are two sets of functions, one set for setting event handlers, one set for setting timer functions. They both do basically same thing: allow you to pass arguments to these callback functions in a much more sane way than the more common (yet in my opinion convoluted) techniques. Their benefits tend to show themselves more as your programs get more complicated. The functions themselves are very simple, but I find them immensely powerful in terms of de-spaghettifying code.
aardvarkTools.setEventHandler : function (element, type, function [, arg1, arg2...])
returns: the same element passed in. "type" should be a string: "onclick", "onmouseover", etc.
This function is a more convenient way to set event handlers on elements, which avoids the downsides of using "closures", or of using a string which is parsed when the event handler is called.
Consider the following code, which creates 5 divs and appends them to the document, giving each an event handler.
for (var i=1; i<=5; i++) {
var elem = document.createElement ("div");
elem.appendChild (
document.createTextNode("element #: " + i));
elem.onclick = function (){
alert("you clicked on element " + i);
};
document.body.appendChild (elem);
}
Each div has its number on it, from 1 to 5. When the div row is clicked on, it pops up an alert box with the element's number.
Except that, well....that's not what happens. No matter which row you click on, the alert box will always lie to you, and tell you that "you clicked on element 6". This is because you have created a "closure", a weird sort of function within a function, which in this case uses the value of the local variable i at the time the event handler is called, rather than at the time it was assigned. Common sense tells us that the variable i doesn't even exist when the event handler is called, but common sense turns out to be wrong.
Closures are interesting from a computer science perspective, I suppose, and many will swear by them for various uses. But to me, their less-than-intuitive behaviour -- and the flaming hoops they make you jump through to do something as simple as assigning an event handler -- tends to make me want to say "OMGWTF?1!!".
Another option is using a string for our onclick. In the example above, we could always do something like this...
elem[i].onclick = "alert(\"you clicked on row " + i + "\")";
Well, technically on certain browsers (*cough* IE *cough*), it may not work unless you do something like:
elem[i].onclick = new Function ("alert(\"you clicked on row " + i + "\")");
Regardless, the string approach has some big limitations of its own, to go along with the obvious convolutedness of having Javascript building strings of more Javascript for it to evaluate later. What if I want to send it something that doesn't "fit" in a string? Say I want to send it a reference to an object? That won't work, of course. And using strings like this just makes for ugly code with lots of quotes and escapes.
Instead, using the aardvarkTools.setEventHandler() function, just pass it a reference to an existing function, pass all the arguments -- strings, numbers, object references, whatever...and it all works, with neither the limitations and yukkiness of strings, nor the insanity of closures:
function rowClickHandler (value) {
alert ("you clicked on element " + value);
}
...
aardvarkTools.setEventHandler(elem[i], "onclick", rowClickHandler, i);
If you need the element that the function was called on (the "this" property), it's there for you as you'd expect. If you need the "event" property (where you can get, for instance, the character that was pressed on keypress events), you can get to it with "this.aaEvtHandler.event", without the typical browser conditional code.
some tips (and additional functions) for function reusability:
Sometimes you may wish to use the same function as an event handler that you call explicitly elsewhere in your program. In this case you probably don't want to get the target element via "this", you'd rather just have the target element come in as a regular argument. You can do this easily, of course, just by passing the element in as an argument.
function setElementColor (elem, color) {
elem.style.color = color;
}
...
var element = el ("div", null, "click me to make me yellow");
setElementColor (element, "green");
aardvarkTools.setEventHandler (element, "onclick", setElementColor,
element, "yellow");
Since setEventHandler() returns the element passed in, it is easy to nest the functions, avoiding the use of a local variable and therefore not having to break the element out of a "tree" of calls to el(). Sometimes this will be handy, other times it will just make your code hard to follow. Your call.
If you write object oriented javascript, you may want to use a method of an object as an event handler. In this case, you will need to explicitly tell the library what your "this" object is....otherwise it won't know just because you sent it a method. To do this, just use the following function:
aardvarkTools.setHandlerThisObject (element, type, thisObject)
returns: the same element passed in
This function allows you to set "this" to refer to any object you want. Call it after setting the event handler, and it will make it so that when the event handler is executed, "this" will refer to whatever you specify.
Finally, if you are using a general purpose function as an event handler, it may be be convenient to force the event handler to return a value of your choice, determined when you set the event handler. This function will do it:
aardvarkTools.setHandlerReturnValue (element, type, returnValue)
returns: the same element passed in
aardvarkTools.setTimerFunction (milliseconds, function[, arg1, arg2...])
returns: a numerical id you can use to cancel the timer
This is an alternative to calling the built-in function setTimeout(). It avoids the yukkiness of strings and closures, just as the setEventHandler() function above does. Again, if you need to explicitly set a "this" object, there is a function to do so. There is also a convenient function that cancels all timers that use the specified callback function, regardless of what their id or arguments are.
aardvarkTools.cancelTimerFunction (id)
pass the id returned by setTimerFunction. No return value.
aardvarkTools.cancelTimerFunctionByFunction (function)
pass the function reference used to set the timer. No return value.
aardvarkTools.setTimerFunctionThisObject (id, thisObject)
pass the id returned by setTimerFunction, and the object you wish to use as "this". Returns the id.
All of these functions are very lightweight and simple (and are public domain, free of any restrictions), so don't be shy about looking at the code, and if they don't do what you want, modify them to your needs. If you want to minimize typing and save a few bytes in your code, you can always create aliases to them by assigning the functions to variables (as we did with "aardvarkTools.createElement" aliased to "el").
setHandler() works by appending an object ("aaEvtHandler") onto the element, which contains the function references and arguments (and optional return value and "this" object).
setTimerFunction() works by saving an array of objects in the aardvarkTools global object. Again, each of these objects contain the function references and arguments (and optional "this" object). When each timer completes, that object is removed from the array.
Feel free to email me if you need help or notice any undesirable behaviour. If you like this stuff, I greatly appreciate mentions and links on your site or blog, as well as on Digg/StumbleUpon/de.licio.us/etc.
Page 1: Aardvark DOM code generator.