Forum Moderators: open

Message Too Old, No Replies

Help on event handlers and their computation.

Simple script weird behaviour :\

         

paolodina

2:28 pm on Sep 14, 2005 (gmt 0)

10+ Year Member



Hi all, hope you can help since I'm on this issue since yesterday and now I'm tolally confused. I'm not a javascript expert, but I read some relevant fact on the "Core JavaScript 1.5 Reference".

I have a web page with two input boxes. On each of them there is an "onclick" event attached. When the user clicks, an alert should appear showing the value contained inside the corresponding box. This doesn't happen, it always displays the value contained in the last box.

This is the code.


<html><head><title>TEST</title>
<script type="text/javascript">
window.onload = function(){
for (var count=0; count<2; count++) {
var name = document.getElementById('name' + String(count))
name.attachEvent("onclick", function() { alert(name.value) })
}
}
</script>
</head>
<body>
<form name="abcdef">
<input type="text" id="name0" value="abc" />
<input type="text" id="name1" value="def" />
</form>
</body>
</html>

Some help would be highly appreciated, TIA.
paolo

Bernard Marx

3:29 pm on Sep 14, 2005 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



References to the inner functions (separate for each iteration) are preserved, which means that they all have access to the outer functions variables. The preservation of the outer's variables in this way is called a closure. The var, name, remains what it was at the end.

// OPTION 1
window.onload = function()
{
for (var count=0; count<2; count++) {
var name = document.getElementById('name' + String(count))
name.onclick = function() { alert(this.value) }
}
}

// OPTION 2
window.onload = function()
{
for (var count=0; count<2; count++) {
var name = document.getElementById('name' + String(count))
name.onclick = myAlert;
}
}

function myAlert()
{
alert(this.value)
}

Even though you will find a number of currently fashionable tutorials suggesting #1, #2 is better, since there is only one function, and there is no closure formed.

However, I don't know how to solve it using

attachEvent
without getting complicated.
attachEvent
doesn't set functions as element methods (
this
remains global). It's a bit pants, really.

paolodina

5:16 pm on Sep 15, 2005 (gmt 0)

10+ Year Member



Hi Bernard. I didn't reply until now because I was trying to understand closure reading here and there. I found very interesting docs (like "Javascript Closures" on jibbering.com, but I still have unclear ideas..

Once again, please, look at this code:


<html><head><title>TEST</title>
<script type="text/javascript">

window.onload = function()
{
for(var count=0; count < 2; count++) {
// Changes to foo0, foo1.
// For each foo exists a bar with the same number.
var obj = document.getElementById('foo' + String(count))
obj.onclick = function() {
alert(String(count) + '--' + this.id)
}
}
}

</script>
</head>
<body>
<input type="text" id="foo0" />
<input type="text" id="foo1" />

</body>
</html>

As you see, the alert always shows the value '2' for the counter variable 'count', and that's not the behaviour I desire.

I know that using 'this' I can get caller object's properties (and probably call its methods). But what should I do if I need to access to some outer variables, like 'count'? I'd need some special reference to the the higher variable scope in the hierarchy. Isn't it?

Can you help me to understand, or even suggest some reading about my particular problem?

Thanks!
paolo

Bernard Marx

9:43 pm on Sep 15, 2005 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Jibbering is probably THE reference for all this, but it seems hard to find one that's less technical (Closures for Dummies - like us).

Local variables and their values are meant to disappear up in smoke as soon as a function has executed (everyone knows that). Closures change the rules.

Imagine that, contrary to 'normal' behaviour, the outer function is still 'alive'. It (or really the call object that holds all it's variables) remains alive for as long as our objects hold references to the function that was defined inside it.

You could say that the outer's variables need to be preserved because the inner may still need to access them. Compare this to the normal relationship of a function, and all the global variables.

The

count
in this inner function refers to the actual *current* value of
count
in the outer function - which is, and will remain at 2 (the loop exit value).

STEP 1

You want to preserve the association of each object with it's respective

count
value.
The natural thing would be to associate this value as a property of each object. When done to document elements, these used to be known as "expando" properties.

The keyword,

this
, is a little special. You might think it still refers to the outer function's
this
(window¦global object). It actually refers, as we want it to do, to each object.

window.onload = function() 
{
for(var count=0; count < 2; count++) {
var obj = document.getElementById('foo' + String(count));
obj.count = count;
obj.onclick = function() { alert( this.count );}
}
}

STEP 2

Even though we have the desired behaviour, STEP 1 is still a dodgy (even dangerous)practice, especially when involving HTML elements.

Avoid the whole sticky closure debacle from the start by taking the inner function, and defining it globally (or anywhere not inside, really). Assign it by reference instead.

window.onload = function() 
{
for(var count=0; count < 2; count++) {
var obj = document.getElementById('foo' + String(count));
obj.count = count;
obj.onclick = myAlert;
}
}

function myAlert()
{
alert( this.count );
}

paolodina

10:34 pm on Sep 15, 2005 (gmt 0)

10+ Year Member



Your explanation is neat and the proposed solution actually works very fine. Anyway, I think that a such situation is quite common and could be considered "a problem", especially if you don't know how to treat it! (And most of the people using javascript for the web is not prepared to face it easily, IMO).

Btw, gathering the net for some additional info on "expando properties" (I never heard about them before your citation), I found a person who encountered exactly the same problem of mine, blogging his experience. If someone is interested and want another resource on this topic, just search for "A huge gotcha with Javascript closures and loops".

Thanks Bernard.

Bernard Marx

11:19 pm on Sep 15, 2005 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I think that a such situation is quite common and could be considered "a problem"

You're right there. For years it's been hidden away. People have only been talking about it openly for a year or so. It may be a little complex, but it should really be mentioned quite early on in JS guides, partly because the the explanation can give you deeper understanding, but mainly because it is something that a great many will run into without having intended to dig deeply for complexities.

I see threads about this alot, and the commonest context is dynamic table creation. Something a dabbling scripter might do in the office.

Anyways, I thought of another solution, but I don't like it because it's probably needlessly inefficient when multiplied out.

- Use the function constructor:

window.onload = function()
{
for(var count=0; count < 2; count++) {
var obj = document.getElementById('foo' + String(count));
obj.onclick = new Function('alert'+count+')');
}
}

If you look at the Gotcha blog, comment #4, you'll find someone suggesting both.
Personally, if I were dealing with a good deal of objects, I'd find some other means. If it were a table for instance, I certainly wouldn't waste time attaching event handlers to every single cell, but catch any required cell events on the TABLE element as they bubble past it, then query them about the source.

BUT it's not ALL a problem. Closures can be put to interesting uses. Emulating private properties & methods is one. They can also be used to divide the script up into largely independant "sub-scripts" with their own namespaces.