Forum Moderators: open

Message Too Old, No Replies

Javascript newbie

using setTimeout and an array of links

         

sunwukung

12:26 pm on Jan 30, 2009 (gmt 0)

10+ Year Member



Can anyone help me understand what's wrong with my application of the setTimeout function? I'm guessing it's a scope issue from what little I've gleaned about working with this function. I'm new to JS, and I'm keen to grasp this function's uses. I've successfully managed to code this effect:

and I want to try and recreate a stripped down version for a list of links.

Here's how I've structured my code. I'm guessing you can see what I'm trying to get at - can someone take a look at it and point me in the right direction? JFTR, it causes a "too much recursion" error at the moment,

##############################################################
JAVASCRIPT

<!--
//--------------------------------------------------------------
//FUNCTION animateLink
//--------------------------------------------------------------
function animateLink(element,value)
{

//clear out any backlog of commands on mouse events
if(element.movement)
{
clearTimeout(element.movement);
}

//acquire xpos from offsetLeft (style.left returns nothing?)
xpos = element.offsetLeft;

//define upper limit of xpos
final_x = value;

//define exit condition
if (xpos == final_x)
{
return true;
}

//increment xpos values until they reach the desired point
if (xpos < final_x)
{
xpos++;
}
if (xpos > final_x)
{
xpos--;
}

//assign incremented value to element's style
element.style.left = xpos + "px";

//use setTimeout to iterate the function until it reaches it's desired state
element.movement = setTimeout(animateLink(element), 40)
}

//--------------------------------------------------------------
//FUNCTION prepareMenu
//--------------------------------------------------------------
function prepareMenu()
{
//acquire document objects
var menu = document.getElementById("menu");
var links = menu.getElementsByTagName("li");

//define events and call respective functions
for(var i = 0; i<links.length; i++)
{
links[i].onmouseover = function()
{
var value = 100;
animateLink(this,value);
}
links[i].onmouseout = function()
{
var value = 0;
animateLink(this,value);
}
}

}

//LOAD SCRIPTS
//-------------------------------------------------------------
window.onload = (prepareMenu);
//-->


##############################################################
XHTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<link rel="stylesheet" media="screen" type="text/css" href="css/screenMenu.css" />
<script type="text/javascript" src="./javascript/animMenu.js"></script>
</head>
<body>
<div id="wrapper">
<!--.........................HEADER......................-->
<div id = "masthead">
<h1>Masthead</h1>
</div>
<!--.........................CONTENT......................-->
<div id= "content">
<ul id = "menu">
<li>Item One</li>
<li>Item Two</li>
<li>Item Three</li>
<li>Item Four</li>
<li>Item Five</li>
<li>Item Six</li>
</ul>
</div>
</div>
<!--.........................FOOTER......................-->
<div class="push">
</div>
</div>
<div id="footer">
<div class="sub-footer">
<p>footer content</p>
</div>
</div>
</body>
</html>

##############################################################
CSS


#menu
{
width: 200px;
float: left;
clear: left;
}

#menu li
{
position: relative;
clear: left;
}

#footer
{
clear: left;
}

I experimented with alerts in the animateLink function to reveal what's happening. It's definitely receiving an event from an LI object (although I've struggled to obtain any other values from that object node). I've tried passing the i value from prepareMenu to this function and alerting it to see if it's picking it up, but it's always a 6, which is a bit wierd. Also, I can't seem to acquire a 'style.left' value in the animateLink function.
Can anyone take a look and explain what's wrong here?

[edited by: coopster at 4:03 pm (utc) on Jan. 30, 2009]
[edit reason] no urls please TOS [webmasterworld.com] [/edit]

astupidname

3:59 pm on Jan 30, 2009 (gmt 0)

10+ Year Member



You have a few small problems, your 3 if statements should be if, else if, else if, and within the setTimeout the function is not quoted, but should instead be wrapped in an anonymous function when passing function that uses arguments to setTimeout or setInterval. Wrapping in anonymous function is quicker than quoting the function, else a string needs to be compiled. And is better to use setInterval for repetitive things (such as moving stuff 1px at a time 100 times) and either include the animation code inside the anonymous function inside setInterval (usually preferred), or place the code in different function and run that function from interval.
Also, you do not initially have a style.left set for the element, that is probably why you could not get it (until the style.left actually gets set later in the function).
Try your script like this:

function animateLink(element,value) {
if (element.movement) {
window.clearInterval(element.movement);
}
xpos = element.offsetLeft;
element.movement = window.setInterval(function () {
if (xpos == value) {
window.clearInterval(element.movement); element.movement=false;
} else if (xpos < value) {
xpos++;
} else if (xpos > value) {
xpos--;
}
element.style.left = xpos + "px";
}, 40);
}

function prepareMenu() {
//acquire document objects
var menu = document.getElementById("menu");
var links = menu.getElementsByTagName("li");
//define events and call respective functions
for (var i = 0; i < links.length; i++) {
links[i].onmouseover = function () {
animateLink(this,100);
};
links[i].onmouseout = function () {
animateLink(this,0);
};
}
}

window.onload = prepareMenu;

I've tried passing the i value from prepareMenu to this function and alerting it to see if it's picking it up, but it's always a 6, which is a bit wierd.

The reason for that is that i is being evaluated from the li element. At the time that i is evaluated from the li element (who's mouseover or mouseout function does retain a pointer back to i within the prepareMenu function the way you would have been trying to assign i to an alert within the li's mouseover/mouseout, that is actually a 'bad' sort of closure which can cause memory leaks) the for loop has finished running and i's value will be read the same from all li elements. The solution to that is to make 'good' use of a closure which returns the value of i and stores it in the returned mouseover function, thereby the mouseover function does not need to retain it's pointer back to the prepareMenu function. Here is an example:

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

window.onload = function () {
var lis = document.getElementsByTagName('li');
for (var i = 0; i < lis.length; i++) {
//using a closure, the value of i is captured and evaluated,
//each time through the loop the outer mouseover function evaluates
//and stores the value of i in the inner function and returns the
//inner function as the value of the lis[i] mouseover
//think of the outer function wrapped in parens as an egg shell which
//gets cracked every time through the loop and spills egg into the inner function
lis[i].onmouseover = (function (egg) {
return function () {
alert(egg);
};
})(i); //i gets passed in to the arg parameter and the anonymous function (with arg parameter) is invoked

//the mousout alert will always alert 3, i's final value at end of loop
lis[i].onmouseout = function () {
alert(i); //retains a pointer back to i in the prepareMenu function
};
}
};

</script>
</head>
<body>
<ul style="width:50px;">
<li>li one</li>
<li>li two</li>
<li>li three</li>
</ul>
</body>
</html>

sunwukung

8:48 pm on Jan 30, 2009 (gmt 0)

10+ Year Member



thank you, thank you, and a thousand times thank you for your detailed reply!

astupidname

10:28 pm on Jan 30, 2009 (gmt 0)

10+ Year Member



You are welcome, glad to be of some help!