Friday, August 13, 2010

Passing arguments to an event handler in Javascript?

Okay, so you want to pass an argument to an event handler in your script? Should be easy, right? let's try...
function buttonClick(buttonNr)
{
    alert('You clicked button nr ' + buttonNr);
}

var button1 = document.createElement('div');
var nr= 1;
button1.onclick = buttonClick(nr);
Piece of cake. Now what's the fuss about, you may be asking. Well try running it...

"You clicked button nr 1"

But you didn't, did you? See the problem?

"But I specifically told it to run buttonClick with parameter 1 when I click the button"

Nope, you didn't. That's what you think you told it to do, but you actually told it to call the function buttonClick with parameter 1 right away (hence the popup), and assign  whatever that function returns to the onclick event. In this case it returns nothing, which means that when you try to click the button, nothing is exactly what happens.

Function name with parenthesis means run the function
Function name without parenthesis is a reference to the function

Take two...

Okay, so we need a function reference. Well, let's just make one on the fly like this then... No parenthesis. We should be good.
button1.onclick = function() { buttonClick(nr) };
Take that, javascript! I passed  a reference to a function that calls the function I want, with the parameter I want. Case closed! problem solved. Now let's call it a day and go home.

Not so fast, mate. Why don't you run a quick test first?

"Undefined???" But I defined it right there!


That may be true, but your event doesn't run right there. It's just assigned there. It's context when it actually runs is whatever element invoked it. In our case the button1 div. So your nr variable is not acessible.

"Well, let's just make the stupid variable global then and go home before dinner gets cold!"

That will work for a while. Until you introduce button 2.

You see you didn't pass the literal number 1 to the function. You passed a reference to the variable containing the number 1. Then you change it to contain the number 2 before you assign it to button 2, and suddenly it's 2 for button 1 as well.

Don't you hate the word reference by now?

Okay, before you start ripping your computer apart, I'll give you the magic formula. here you go. Do this, and nobody has to get hurt:
button1.onclick = (function (nr) {return function() {buttonClick(nr);} })(nr)
Huh?

Okay, let's break it down. A good programmer learns from his mistakes. What have we learned from our two previous mistakes?
  1. That when you put a parenthesis after a function it is called right away, and the result is assigned to the event handler.
  2. That the event handler function needs to run in a scope that has the parameter as a local variable.
Our solution is really a combination of those two lessons. Let's look at what we are in essence doing, by wasting some space. What if I put it like this?
contextWrapper = function(nr)
{
    var getEventHandler = function()
    {
        buttonClick(nr);
    }

    return getEventHandler;
}

button1.onclick = contextWrapper(nr);
So what we're doing is repeating our first mistake, but this time taking advantage of the fact that it calls the function and assigns it's return value to the event handler. And we're executing buttonClick in a context that retains its proper value for the nr variable.

So what is the value of button1.onclick after this? It's "function () { buttonClick(nr) }", where nr is fetched from the contextWrapper object surrounding it. So it retains its original value, no matter what you do.

Smart, eh? Now go make some crazy dynamic menus and tab bars and stuff and put it to use. Enjoy!
Related Posts Plugin for WordPress, Blogger...