09 August, 2006

Passing an object reference to a function

Here is another proof of Javascript spectacular flexibility. We want to draw 10 DIV in a page, representing 10 people. When we click a DIV, we want a message indicating which person was clicked. Given an object named person, we could hard code this doing :

div.innerHTML = person.firstName+' '+person.lastName;
div.onclick = function() {
alert(this.innerHTML+' was clicked');
}

But the object model would be useless in this case !
Our first try is to make a loop that creates 10 DIV and 10 objects and map each of it together. Let's look how it works :

<div id="myGroup" style="border: 1px dashed #000; padding: 5px; width: 300px;"></div>

And the script :

//A class Person
function Person( firstName, lastName ) {
this.firstName = firstName;
this.lastName = lastName;
}
//A function to be executed when onclick is fired
function clickFunction(person) {
alert(person.firstName+' '+person.lastName+' was clicked');
}
//We create 10 DIV representing 10 people
for (var i=0; i < 10; i++) {
//Create a DIV
var div = document.createElement('DIV');
//Create an object
var person = new Person('firstName'+i, 'lastName'+i);
//Fill in the DIV with the object infos
div.innerHTML = person.firstName+' '+person.lastName;
//Some styling
div.style.border = '1px solid #CCC';
div.style.margin = '5px';
div.style.cursor = 'pointer';
//Set the event on the DIV
div.onclick = function() {
//Use the object matched with the DIV in a function
clickFunction(person);
}
document.getElementById('myGroup').appendChild(div);
}

We have a DIV named myGroup which contains our 10 DIV
In each loop, we create a Person 'var person = ...' that is referred by the 'onclick' function of the DIV.
Try clicking on the people of MyGroup, you'll be quickly disappointed.
It refers to the last Person for any DIV we click on. WHY ?
Because the variable 'person' is re-written in each loop for a new Person. In the end, it lasts only 1 Person, person number 9!

One solution could be to push each Person in an Array and refer to the position of the Person in the 'onclick' function of the DIV. But Javascript has more to offer than this hack!
In Javascript, when a function creates a local variable that is refered by other functions, the variable is stored in memory until its last use.
We are going to exploit this capability like this :

//A class Person
function Person( firstName, lastName ) {
this.firstName = firstName;
this.lastName = lastName;
}
//A function to be executed when onclick is fired
function clickFunction(person) {
alert(person.firstName+' '+person.lastName+' was clicked');
}
//A function that is used to create a DIV matched with an object
function createDiv(index) {
//Create a DIV
var div = document.createElement('DIV');
//Create an object
var person = new Person('firstName'+index, 'lastName'+index);
//Fill in the DIV with the object infos
div.innerHTML = person.firstName+' '+person.lastName;
//Some styling
div.style.border = '1px solid #CCC';
div.style.margin = '5px';
div.style.cursor = 'pointer';
//Set the event on the DIV
div.onclick = function() {
//Use the object matched with the DIV in a function
clickFunction(person);
}
document.getElementById('myGroup').appendChild(div);
}
//We create 10 DIV representing 10 people
for (var i=0; i < 10; i++) {
createDiv(i);
}

What we do here is to create an intermediate function that aims to create local variables. Now the variable 'person' is stored at each loop, event if it has the same name! And each 'onclick' function refers to the right 'person'.

3 comments:

Drew said...

Can this be done without creating div's dynamically ?
What if my div is already in the body of my document ?

Nicolas Faugout said...

Yes you can. If your DIV is already in the document, you'll need to get a pointer to it using the "document.getElementById" function. It implies that you give an ID to your DIV. If you have a group of DIVs, you can as well give them a CLASS and use the $$ function from Prototype framework that retrieves those DIVs based on their class attribute.

Anonymous said...

Thanks Nick,

Wow,
I've spent hours trying to figure this one out.
I'm still getting my head around it, but you cleared it up for me.

Yea thanks,

Dave Cushman