Logan Serman
Logan Serman

Reputation: 29860

Javascript's setTimeout function not calling

The setTimeout function always seems to give me trouble. Right now I have a function that is recursive (calls itself through setTimeout) and changes the elements height.

The function is sent two arguments: the element to be altered and that elements maximum height. The purpose of the function is to unfold the element, or "slide down" at a constant pace. I'm aware I could probably solve this problem with jQuery, but I'm trying my own function.

function slide_down(element, max_height)
{   
    if(element.clientHeight < max_height)
    {
        var factor = 10;
        var new_height = (element.clientHeight + factor >= max_height) ? max_height : (element.clientHeight + factor);
        element.style.height = new_height + 'px';

        var func_call = 'slide_down(' + element + ', ' + max_height + ');';
        window.setTimeout(func_call, 10);
    }
}

I have tested the arguments when the function is initially called. max_height is set to 20 because it is the elements desired height (I got this value from .scrollHeight).

When the function is done, I want to have it call itself until max_height is the element's height. I do that with this setTimeout call:

var func_call = 'slide_down(' + element + ', ' + max_height + ');';
window.setTimeout(func_call, 10);

And it is not working. THIS does work:

var func_call = 'alert(1);';
window.setTimeout(func_call, 10);

I have also tried putting the function call directly into the setTimeout statement, still does not work.

Note that the element's height DOES change the first iteration, the function just never calls itself. So the element variable is set correctly, and I used alert() to display the max_height which is also correct.

alert(func_call);

Alerts this:

slide_down([object HTMLParagraphElement], 20);

Upvotes: 5

Views: 23745

Answers (6)

annakata
annakata

Reputation: 75794

First of all, this "Right now I have a function that is recursive (calls itself through setTimeout)" screams setInterval to me if you're not varying the period.

Secondly, this is because the element reference is lost in the string concat as element.toString() will be "[object]". Try passing in an id you can re-find, store a reference a level up, or (as I've just seen matt b point out) the expanded method form.

Upvotes: 3

Tokimon
Tokimon

Reputation: 4142

A more subtle way of writing the method (as extension to Gregs answer):

function slide_down(element, max_height)
{   
    if(element.clientHeight < max_height)
    { return; }

    var factor = 10,
        new_height = element.clientHeight + factor,
        f = arguments.callee;

    if( new_height > max_height )
    { new_height = max_height; }

    element.style.height = new_height + 'px';

    window.setTimeout(function() { f(element, max_height); }, 10);
}

The real advantage of this sample code is the use of arguments.callee. In this way you won't be breaking your function, should you decide to rename it. I also simplyfied the new_height value assigning. The problem in the old code is that you performed element.clientHeight + factor twice, which is kinda unnessesary. Not that it has any noticible effect on your performance in this case. But it's always good to avoid calculating the same things over and over, but just store the result for later use.

Upvotes: 2

matt b
matt b

Reputation: 139921

Two things:

1. There is an alternate way to call setTimeout() in which you pass the function and parameters, rather than a string to execute. In fact, the Gecko DOM manual states that your version of calling setTimeout() is not recommended.

Instead of:

var func_call = 'slide_down(' + element + ', ' + max_height + ');';
window.setTimeout(func_call, 10);

Try this instead:

window.setTimeout(slide_down, 10, element, max_height);  

Update: as RoBorg mentioned, this may not work in IE, so you might as well ignore it.


2. I have a feeling that calling setTimeout() from within a function that was called itself by setTimeout() is either not allowed or does not work well. I assume that you are chaining the calls like this because you want the code to execute repeatedly? If so, that is what window.setInterval() is for.

Upvotes: 0

Sergey Ilinsky
Sergey Ilinsky

Reputation: 31535

The problem with your code is that you are fail to pass element parameter.

Instead of:

var func_call = 'slide_down(' + element + ', ' + max_height + ');';

window.setTimeout(func_call, 10);

try using anonymous function:


window.setTimeout(function() {
    slide_down(element, max_height)
}, 10);

@matt.b: arguments passing into setTimeout call (the way you do) is not supported in IE.

Upvotes: 1

Greg
Greg

Reputation: 321578

When you do this:

var func_call = 'slide_down(' + element + ', ' + max_height + ');';

you're converting element to a string, so your timeout will look like

slide_down("[Object]", 100);

which obviously won't work.

What you should be doing is creating a closure - it's a little complicated but basically you create a function, and local variable from the old function are still available within the new function.

function slide_down(element, max_height)
{   
    if(element.clientHeight < max_height)
    {
        var factor = 10;
        var new_height = (element.clientHeight + factor >= max_height) ? max_height : (element.clientHeight + factor);
        element.style.height = new_height + 'px';

        var func = function()
        {
            slide_down(element, max_height);
        }

        window.setTimeout(func, 10);
    }
}

Also 10 is a bit short for a timeout - I'd recommend using 50 as a minimum.

Upvotes: 19

Geoff
Geoff

Reputation: 9340

I had a similar experience. When you are passing element the next time through, it is getting converted to a string in the function call, so when the function runs, it tries to find an element named [object].string or something, instead of what you intended.

Try changing your function parameter to take the id of the element and do a document.getElementById() call first thing inside the function.

Upvotes: 1

Related Questions