PDA

View Full Version : Simplifying some code.



dog
10-03-2007, 08:45 PM
Hi,

I've written some functions to display and not display things.



function displayBlock(elementName) {

obj = document.getElementById(elementName);
obj.style.display="block";
}

function displayNone(elementName) {

obj = document.getElementById(elementName);
obj.style.display="none";
}


I want to use the code to display a large image when the user clicks on a thumbnail and hide all the other large images.

Here's an example of how I'm writing the HTML when dealing with just three images and it's already getting a bit much to define all the images I don't want to display.


<div id="thumbnails">
<a href="javascript:displayBlock('img1'); displayNone('img2'); displayNone('img3');"><img src="thumb1.jpg" /></a>
<a href="javascript:displayBlock('img2'); displayNone('img1'); displayNone('img3');"><img src="thumb2.jpg" /></a>
<a href="javascript:displayBlock('img3'); displayNone('img1'); displayNone('img2');"><img src="thumb3.jpg" /></a>
</div>

<div id="largeImage">
<div id="img1">
<img src="img1.jpg" />
</div>
<div id="img2">
<img src="img2.jpg" />
</div>
<div id="img3">
<img src="img3.jpg" />
</div>
</div>


Can someone help me modify my code so rather than saying, 'Show this and don't show that, that or that' I can just say, 'Show this and don't show anything else.'?

Thanks for any responses.

Dog

Trinithis
10-04-2007, 05:05 AM
<div id="thumbnails">
<a href="javascript:void(0);"><img src="thumb1.jpg" /></a>
<a href="javascript:void(0);"><img src="thumb2.jpg" /></a>
<a href="javascript:void(0);"><img src="thumb3.jpg" /></a>
</div>

<div id="largeImage">
<div id="img1">
<img src="img1.jpg" />
</div>
<div id="img2">
<img src="img2.jpg" />
</div>
<div id="img3">
<img src="img3.jpg" />
</div>
</div>

<script type="text/javascript">
(function() {
var thumbs = document.getElementById("thumbnails").getElementsByTagName("a");
var largeImgs = document.getElementById("largeImage").getElementsByTagName("div");
var n = thumbs.length;
function toggle(x) {
for(var i=0; i<n; ++i)
if(i!=x) largeImgs[i].style.display = "none";
else largeImgs[i].style.display = "block";
}
for(var i=0; i<n; ++i)
if(document.addEventListener)
thumbs[i].onclick = (function() {
var x = i;
return function(e) {
toggle(x);
};
})();
})();
</script>

dog
10-04-2007, 01:06 PM
Hey Trinithis,

Thanks for the code. I've tried putting it in place but so far I'm not getting any results at all.

How does the function get called? What does "javascript:void(0);" do?

I just noticed that in my original post I didn't define the CSS I was using to hide the large images. Here's a copy of the whole page.



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Test 1b</title>

<script type="text/javascript">
function displayBlock(elementName) {

obj = document.getElementById(elementName);
obj.style.display="block";
}

function displayNone(elementName) {

obj = document.getElementById(elementName);
obj.style.display="none";
}
</script>

<style type="text/css">
#thumbnails img { width: 74px; height: 44px; }
#img2, #img3 { display: none; }
</style>

</head>

<body>

<div id="thumbnails">
<a href="javascript:displayBlock('img1'); displayNone('img2'); displayNone('img3');"><img src="../img/74x44/pedro_1.jpg" /></a>
<a href="javascript:displayBlock('img2'); displayNone('img1'); displayNone('img3');"><img src="../img/74x44/pedro_2.jpg" /></a>
<a href="javascript:displayBlock('img3'); displayNone('img1'); displayNone('img2');"><img src="../img/74x44/pedro_3.jpg" /></a>
</div>

<div id="largeImage">
<div id="img1" >
<img src="../img/350/pedro_1.jpg" width="250" height="158" />
</div>
<div id="img2">
<img src="../img/350/pedro_2.jpg" width="250" height="178" />
</div>
<div id="img3">
<img src="../img/350/pedro_3.jpg" width="350" height="261" />
</div>
</div>

</body>
</html>


The effect I'm looking for is:

When the user clicks on thumbnail 2 for example, 'img2' gets display and all the other divs inside the largeImages div get set to not display. In the code above it's working. In the real site there are many, many images so I want a way to reduce:

displayBlock('img1'); displayNone('img2'); displayNone('img3');
into

displayBlock('img1'); displayNoneEverythingExcept('img1');

I liked the bit of your code where you defined the variables 'thumbs' and 'largeImgs':

var thumbs = document.getElementById("thumbnails").getElementsByTagName("a");
var largeImgs = document.getElementById("largeImage").getElementsByTagName("div");
I didn't know how to identify all the elements of a div like that. What you did after that I don't understand.

Thanks agains for the help.

Peace!
Dog

Trinithis
10-04-2007, 04:54 PM
Oops, I forgot to take out the if(document.addEventListener). That would cause problems in IE.
The code should be

(function() {
var thumbs = document.getElementById("thumbnails").getElementsByTagName("a");
var largeImgs = document.getElementById("largeImage").getElementsByTagName("div");
var n = thumbs.length;
function toggle(x) {
for(var i=0; i<n; ++i)
if(i!=x) largeImgs[i].style.display = "none";
else largeImgs[i].style.display = "block";
}
for(var i=0; i<n; ++i)
thumbs[i].onclick = (function() {
var x = i;
return function(e) {
toggle(x);
};
})();
})();

javascript:void(0) means do nothing and return nothing.


Now an explanation:


for(var i=0; i<n; ++i)
thumbs[i].onclick = (function() {
var x = i;
return function(e) {
toggle(x);
};
})();

This sets up a loop that iterates n times, where n is defined above as thumbs.length.
Then for each element in thumbs, thumbs[i], I tell it to react to a user's click, via onclick.
Now I need to explain something that might be a little confusing:


var myFunc = function(){something};
This creates a function that does something when activated, aka, through myFunc();

Now if you did:

function(){something};
This creates a function that would do something if activated. But the question is how? It is not attached to a variable such as myFunc. This is called an anonymous function. It has no name.
However, there are uses for them even if you can't call them whenever you want.

One thing to notice is that if you take the variable a function is stored in, you could do this:

(myFunc)();
This is equivalent to:

myFunc();

This is analogous to parenthesis around numbers:

5+4
(5+4)
(5)+4
All these do the same thing.

To understand why this works with functions requires an understanding of variable references. For example:

var func1 = function(){ alert('hi'); };
var func2 = func1;

The above code creates two variables, func1 and func2, but there is only one function. When you do

var func2 = func1;
you are not copying the entire function(){alert('hi');} into func2... only references to it. Rather when you do

var func1 = function(){ alert('hi'); };
Javascript creates a function, function(){alert('hi');}, and stores it in the computer's memory somewhere. Then when the assignment happens, instead of giving func1 the function, the computer stores into func1 a reference of the function stored in memory.
In other words, the computer tells func1 where to find the function. So when you do

func1();
the computer asks func1 what is is holding. It finds out it is holding a reference to a function. Then it asks, where is this function located, and func1 returns the actual function. Once returned, the computer takes that function and activates it.

Anyway, (myFunc)() works because myFunc will return the function, and you can think of it as for a split second, the actual function sits around within those parenthesis before becoming activated via the ().

The reason why this matters is because you can use this principle with anonymous functions. Say you had the anonymous function:

function(x){ alert(x); }
And you wanted to execute it immediately, right when you made it.
You could do

(function(msg){ alert(msg); })("hello");
Note where the "hello" is. Just like a normal function, you still have to supply arguments, in this case, for msg.
Now, you might be wondering what the point of creating a one-time function and executing it immediately. Why not write the code outside of a function if you are going to do that?
The key is that functions create their own scope, meaning variables made inside it are only known to the function. This means the outside world can't change what's happening inside it.

So in my code I do:

(function() {
var x = i;
return function(e) {
toggle(x);
};
})();

First I created an anonymous function and immediately execute it once made. Inside the function, I make a variable called x and assign it the value i.
While x cannot be seen from the outside, the function can see variables that are outside it (to a certain extent). So it knows i.
Then it creates a function

function(e) { toggle(x); };
and returns the reference of that function via the return keyword to thumbs[i].onclick.
A useful thing about Javascript is that it allows something called a closure. I'll get around to that in a sec.

Normally, when a function is finished executing, all the local variables it creates vanish. However, if you can return something that can reference at least one of those local variables, then the local variables can live beyond the life of their function. At the same time, they are still considered local. Things that were not created in the function still cannot see or touch them.
In my case, I return a new (and anonymous) function that makes reference to the local variable x. Since this function was returned and therefore can be used outside of the function that created it, and at the same time, it uses the local variable x of it's parent function, x does not die.
This is the essence of a closure: to make variables that are local within a function, but linger after a function has died.


thumbs[i].onclick = function(e){something};

Means that whenever that element is clicked, do that function. All functions assigned as events (such as clicking) need the paramater e as their first and only paramater.

It should be noted that thumbs[i].onclick is assigned a function reference, in this case, the reference of the

function(e) { toggle(x); }
that was returned.

Now, all the thumbs[i]'s have been assigned event handlers (function for events). Each of these handlers have their own local variable x that cannot and will not interfere with one another despite having the same name.
So when clicked, the anonymous function assigned to the onclick will activate toggle(x).

The function toggle says hide all the images in largeImgs, except for the x'th image (rememeber, there is a 0'th index).

To tie things up, I wrapped my entire code within an anonymous function that I immediately execute so that you wouldn't get variable name collisions. (Like a self-inclosed namespace.)



I hope this makes sense :D

dog
10-04-2007, 05:58 PM
Trinithis,

Thanks so much. Works a treat!

I can't say I understand everything in the explaination just yet but I really appricate having it.

This is going to be one thread I come back to again and again.

I do have one question already. After mistakenly placing the code in the head and then in the body before the HTML I realise the code only works when it comes after the HTML in the body.

Why is that?

I'm sure I'll be back with more questions before to long but first I'm going to read over your explaination a few times.

Thanks again :)

Trinithis
10-04-2007, 09:17 PM
When you do getElementById("something"), if the code is placed before the element (the tag), then the getElementById thinks the element does not exist. You don't have to place it after the entire body tag... just after the tags it references. getElementsByTagName works similarly.

Trinithis
10-04-2007, 10:26 PM
Here's a simple demo that might help you understand closures:



var num = 9;

function incrementer() {
var num = 0; //different than the num outside
return function() { alert(++num); };
}

var x1 = incrementer();
var x2 = incrementer();

x1(); //alerts 1
x1(); //alerts 2
x2(); //alerts 1
x1(); //alerts 3
x2(); //alerts 2


Compared to:


var num = 0;

function incrementer() {
return function() { alert(++num); };
}

var x1 = incrementer();
var x2 = incrementer();

x1(); //alerts 1
x1(); //alerts 2
x2(); //alerts 3
x1(); //alerts 4
x2(); //alerts 5

The first code example uses a closure. The second does not.