Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 8 of 8
  1. #1
    Senior Coder Arbitrator's Avatar
    Join Date
    Mar 2006
    Location
    Splendora, Texas, United States of America
    Posts
    3,277
    Thanks
    28
    Thanked 272 Times in 266 Posts

    Preserving a Variable without eval()

    My code is shown below and a live example 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?

    Code:
    <!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:

    Code:
    				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);");
    				}
    For every complex problem, there is an answer that is clear, simple, and wrong.

  • #2
    Supreme Master coder! _Aerospace_Eng_'s Avatar
    Join Date
    Dec 2004
    Location
    In a place far, far away...
    Posts
    19,291
    Thanks
    2
    Thanked 1,043 Times in 1,019 Posts
    I can't remember why this works. Hopefully someone else can shed some light on it.
    Code:
    				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);
    				}
    ||||If you are getting paid to do a job, don't ask for help on it!||||

  • #3
    Senior Coder Arbitrator's Avatar
    Join Date
    Mar 2006
    Location
    Splendora, Texas, United States of America
    Posts
    3,277
    Thanks
    28
    Thanked 272 Times in 266 Posts
    Quote Originally Posted by _Aerospace_Eng_ View Post
    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. The relevant slice of code now looks like:

    Code:
    (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.
    For every complex problem, there is an answer that is clear, simple, and wrong.

  • #4
    Supreme Master coder! _Aerospace_Eng_'s Avatar
    Join Date
    Dec 2004
    Location
    In a place far, far away...
    Posts
    19,291
    Thanks
    2
    Thanked 1,043 Times in 1,019 Posts
    Hmm seemed to work when I tried it. Glad you got it working though.
    ||||If you are getting paid to do a job, don't ask for help on it!||||

  • #5
    Regular Coder
    Join Date
    Jun 2007
    Location
    USA
    Posts
    527
    Thanks
    26
    Thanked 74 Times in 72 Posts
    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.
    Code:
    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);
    	}
    Last edited by Trinithis; 06-08-2007 at 03:38 AM.

  • #6
    New Coder
    Join Date
    May 2006
    Posts
    62
    Thanks
    0
    Thanked 0 Times in 0 Posts
    The problem is the scope and context in which your event listener is called.

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

    Code:
    (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:

    Code:
    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).
    Fiji Web Design - where i do Joomla Web Design
    Bucabay.com - My blog

  • #7
    Senior Coder Arbitrator's Avatar
    Join Date
    Mar 2006
    Location
    Splendora, Texas, United States of America
    Posts
    3,277
    Thanks
    28
    Thanked 272 Times in 266 Posts
    Quote Originally Posted by Trinithis View Post
    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.

    Quote Originally Posted by digital-ether View Post
    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.
    For every complex problem, there is an answer that is clear, simple, and wrong.

  • #8
    New Coder
    Join Date
    May 2006
    Posts
    62
    Thanks
    0
    Thanked 0 Times in 0 Posts
    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:

    Code:
    $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

    Code:
    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:
    Code:
    function() {
    /* code */
    }
    Fiji Web Design - where i do Joomla Web Design
    Bucabay.com - My blog


  •  

    Posting Permissions

    • You may not post new threads
    • You may not post replies
    • You may not post attachments
    • You may not edit your posts
    •