...

View Full Version : Preserving a Variable without eval()



Arbitrator
06-07-2007, 04:29 AM
My code is shown below and a live example (http://www.jsgp.us/demos/D0001.html) is also available. The live example does not include the JavaScript comments shown below (in blue).

Basically, Iím wondering what the best way to pass the variable i to the function in the event listener would be. I know how to do it using eval(), but, as Iíve heard numerous times, use of that method is poor practice and there are (always?) more efficient workarounds. So, whatís the right way to do it?


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

<html lang="en-Latn-US">
<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Demo D0001</title>
<meta name="Author" content="Patrick Garies">
<meta name="Created" content="2007-05-29">
<meta name="Revised" content="2007-05-31 (T2007-06-06)">
<style type="text/css">
* { margin: 0; padding: 0; }
html { background: white; color: black; font-size: 16px; }
dl { width: 200px; margin: 1em auto; border: 1px solid; padding: 1em; }
dt { margin: 0 0 1em; font-weight: bold; }
.inactive { display: none; }
.caption { margin: 0 0 1em; text-align: center; text-transform: capitalize; }
.preposition { text-transform: lowercase; }
#galleryImages { padding-bottom: 0; }
#galleryImages img, .active, .caption { display: block; }
#galleryImages img { display: block; margin: 0 auto 1em; }
#galleryNavigation dd { padding: 0.3em 0.5em; background: #eee; cursor: pointer; }
#galleryNavigation dd:hover { background: #ddd; }
#galleryNavigation dd.active { background: #ddd; color: silver; cursor: default; }
</style>
<script type="application/ecmascript">
document.defaultView.addEventListener("load", function () {
var galleryImages = document.getElementById("galleryImages").getElementsByTagName("dd");
var galleryNavigation = document.getElementById("galleryNavigation").getElementsByTagName("dd");
for (var i = 0; i < galleryNavigation.length; i++) {
galleryNavigation[i].addEventListener("click", function () {
resetGallery();
this.setAttribute("class", "active");
//galleryImages[i].setAttribute("class", "active");
//the below statement would be removed with the above statement in a functional state
document.getElementById("article" + this.firstChild.data.charAt(this.firstChild.data.length - 1)).setAttribute("class", "active");
}, false);
}
function resetGallery() {
for (var j = 0; j < galleryImages.length; j++) {
galleryImages[j].setAttribute("class", "inactive");
}
for (var j = 0; j < galleryNavigation.length; j++) {
galleryNavigation[j].removeAttribute("class");
}
}
resetGallery();
galleryImages[0].setAttribute("class", "active");
document.getElementById("galleryNavigation").removeAttribute("class");
}, false);
</script>

</head>
<body>

<div id="gallery">
<dl id="galleryImages">
<dt>Gallery Images</dt>
<dd id="articleA"><img alt="Article A" width="150" height="150" src="article_a.png"> <span class="caption">the letter A <span class="preposition">on</span> crimson</span></dd>
<dd id="articleB"><img alt="Article B" width="150" height="150" src="article_b.png"> <span class="caption">the letter B <span class="preposition">on</span> denim</span></dd>
<dd id="articleC"><img alt="Article C" width="150" height="150" src="article_c.png"> <span class="caption">the letter C <span class="preposition">on</span> gold</span></dd>
<dd id="articleD"><img alt="Article D" width="150" height="150" src="article_d.png"> <span class="caption">the letter D <span class="preposition">on</span> viridian</span></dd>
<dd id="articleE"><img alt="Article E" width="150" height="150" src="article_e.png"> <span class="caption">the letter E <span class="preposition">on</span> amethyst</span></dd>
<dd id="articleF"><img alt="Article F" width="150" height="150" src="article_f.png"> <span class="caption">the letter F <span class="preposition">on</span> walnut</span></dd>
</dl>
<dl id="galleryNavigation" class="inactive">
<dt>Gallery Navigation</dt>
<dd>Article A</dd>
<dd>Article B</dd>
<dd>Article C</dd>
<dd>Article D</dd>
<dd>Article E</dd>
<dd>Article F</dd>
</dl>
</div>

</body>
</html>

For the sake of comprehension, using eval() on the above code would yield:


for (var i = 0; i < galleryNavigation.length; i++) {
eval("galleryNavigation[i].addEventListener(\"click\", function () {\
resetGallery();\
this.setAttribute(\"class\", \"active\");\
galleryImages[" + i + "].setAttribute(\"class\", \"active\");\
}, false);");
}

_Aerospace_Eng_
06-07-2007, 05:45 AM
I can't remember why this works. Hopefully someone else can shed some light on it.

for (var i = 0; i < galleryNavigation.length; i++) {
el = galleryImages[i];
galleryNavigation[i].addEventListener("click", function () {
resetGallery();
el.setAttribute("class", "active");
document.getElementById("article" + this.firstChild.data.charAt(this.firstChild.data.length - 1)).setAttribute("class", "active");
}, false);
}

Arbitrator
06-07-2007, 09:50 PM
I can't remember why this works. Hopefully someone else can shed some light on it.I attempted to use your code, but it didnít work. Thanks for the attempt, at least.

Anyway, I solved the issue using something that I believe is called a function literal. The technique is outlined in the article Practical (& Functional) JavaScript Snippets (http://www.svendtofte.com/code/practical_functional_js/). The relevant slice of code now looks like:


(function () {
var n = i;
galleryNavigation[i].addEventListener("click", function () {
resetGallery();
galleryImages[n].setAttribute("class", "active");
this.setAttribute("class", "active");
}, false);
})();The live example, referenced in the first post of this thread, has also been updated.

_Aerospace_Eng_
06-07-2007, 11:02 PM
Hmm seemed to work when I tried it. Glad you got it working though.

Trinithis
06-08-2007, 04:31 AM
I wrote this before I realized you solved it, but here is a similar approach to your solution, but the function literal (as you called it?) returns a function instead.


for(var i=0; i<galleryNavigation.length; ++i) {
galleryNavigation[i].addEventListener("click",
(function(i) { return (function() {
resetGallery();
this.setAttribute("class", "active");
galleryImages[i].setAttribute("class", "active");
});
})(i)
, false);
}

digital-ether
06-08-2007, 05:20 AM
The problem is the scope and context in which your event listener is called.


(function () {
var n = i;
galleryNavigation[i].addEventListener("click", function () {
resetGallery();
galleryImages[n].setAttribute("class", "active");
this.setAttribute("class", "active");
}, false);
})();

The part:


(function() { var n = i; .... })();

Calls the lambda function, right after its defined. Thus i is assigned to n right then, in that scope.

The event handler is another lambda function:


function () {
resetGallery();
galleryImages[n].setAttribute("class", "active");
this.setAttribute("class", "active");
}

As its defined, n is found in the function definition. This causes JS to save the current context/scope of the "function where the function is defined" (outter function), in the scope of the function (inner function). So "n" creates a JavaScript closure, so that it will remain available to the function until the closure is removed (deleting n).

Arbitrator
06-10-2007, 01:11 AM
I wrote this before I realized you solved it, but here is a similar approach to your solution, but the function literal (as you called it?) returns a function instead.ďfunction literalĒ is the term used in the referenced article and the search term that I used to find the article, so I can only assume that itís right.

Anyway, I find your solution more elegant because I donít have to create a new variable and because all of the extra stuff is contained within the event listener statement instead of vice versa. Iíve implemented it in the live example.


The problem is the scope and context in which your event listener is called.

[...]

Calls the lambda function, right after its defined. Thus i is assigned to n right then, in that scope.

[...]

As its defined, n is found in the function definition. This causes JS to save the current context/scope of the "function where the function is defined" (outter function), in the scope of the function (inner function). So "n" creates a JavaScript closure, so that it will remain available to the function until the closure is removed (deleting n).Thanks for the informative breakdown.

I tried looking up the term ďclosureĒ for this issue and found nothing, so I ended up looking up ďfunction literalĒ instead. I still donít really understand the concepts of a function literal, closure, or lambda function. Guess Iíll pick it up eventually.

digital-ether
06-11-2007, 11:47 AM
The function literal is nothing special, its just cleaner markup/syntax. You could have used a regular function in place of the function literal. The special feature was the closure...

To help you in your search:

try combinations of 'closure' and 'scope' together. Another two common uses of closures is in XHR (XMLHTTPRequest) onreadystate() callback, and setTimeout()/setInterval(). It's used for the same thing, preserving the scope/environment in the definition of the function, so it exists when the function is called.

As for the jargon, my understanding is:

The function literal is the explicit function, and not a reference.

Eg:



$myfunc = function() { /* code */ }; // function definition/declaration
(myfunc)(); // calling a function implicitly via the reference "myfunc". The actual code is in the function definition to which we have a reference
(function() {/*code */})(); // calling the "function literal" explicitly, there is no function reference, just the function definition being "called".

A similar term is the "object literal".

eg: Object Literal


var obj = new Object(); // object definition
obj; // an object reference
{/*object */}; // an object literal

eg:

A JS closure is a reference to a preserved scope. Think of it as the environment around the variable that created the closure, frozen in time... as in the solutions given.

A lambda function is just a literal function definition - an anonymous function.

eg:


function() {
/* code */
}



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum