views:

200

answers:

2

Hello everybody,

I have a list of items in a page that must be hidden in sequence, but just after the previous item has been totally hidden.

I made the following code, where I create a big string inserting the callbacks inside the previous callbacks and later use eval to execute the effects, but despite the code is working fine as expected, I'm totally sure that's not the best way to do this.

// get items to hide
    var itemsToHide = jQuery(".hide");

// function responsible to hide the item
    var hideItem = function (item, callback) {
     jQuery(item).hide(100, callback)
    };

// declare an empty function, to be the first to be called
    var buff = function(){};

    for (var i = 0; i < itemsToHide.length; i++) {
// put the previous value of buff in the callback and assign this to buff
     buff = "function(){hideItem(itemsToHide[" + i + "], " + buff + ");}";
    }

// execute the effects
    eval("(" + buff + ")()");

Any suggestion on how to accomplish this effect using recursion, without the "evil" eval?

+3  A: 

Since you know the number of milliseconds each one takes to hide, you don't need to use a callback at all:

jQuery(".hide").each(function(i)
{
    var obj = $(this);
    setTimeout(function() { obj.hide(100); }, i * 100);
}
Greg
Agreed, I have done this before and it works well. Damn you greg, you beat me too it!!
Zoidberg
+6  A: 

Hiding with recursion - functional style

In this case you know the duration of the effect, so you can do as others suggested.

However you might still want to know how to achieve the same using a more functional style, using a simple recursion.

So here I show you a way to do that.

1. The code

<script src="http://code.jquery.com/jquery-latest.pack.js"&gt;&lt;/script&gt;
<script type="text/javascript">
    $(function() {
        var items = jQuery(".to-hide");
        (function hideRec() {
            if (items.length == 0) {
                window.alert("The end.");
                return;
            }
            var toHide = jQuery(items[0]);
            items = items.slice(1);
            toHide.hide("100", hideRec);
        })();
    });
<script>

<ul>
    <li class="to-hide">...</li>
    <li class="to-hide">...</li>
    <li class="to-hide">...</li>
    <li class="to-hide">...</li>
    <li class="to-hide">...</li>
<ul>

2. How does it work?

We first obtain, from jQuery, an array containing the objects we want to-hide.

Then we create a named anonymous function that will be executed right after its definition -- it's the (function() { ... })(); pattern. Even if it is an anonymous function, we give it a name so that we are able to easily call it recursively.

Note that you can achieve the same thing without giving an anonymous function a name -- which may sound a little strange if you don't understand how JavaScript deals with scope -- with the more obscure version:

(function() {                            // Anonymous function without a name
    if (items.length == 0) {
        window.alert("The end.");
        return;
    }
    var toHide = jQuery(items[0]);
    items = items.slice(1);
    toHide.hide("100", arguments.callee); // Recursive call to anonymous function
})();

This time we used the fact that arguments.callee represents the function itself -- so no need to give an anonymous function a name.

The whole magic is inside this recursive anonymous function -- that is a closure.

The hideRec anonymous function captures the items array. After checking that there's still something inside, it will remove the first element and store it in the local toHide variable (properly wrapped with jQuery).

At the end, it uses jQuery to hide the element, and passes this anonymous function as the callback -- jQuery will call it again once it has finished the effect.

I hope this is clear enough.

Good luck!

Bruno Reis
Just perfect. Thank you very much. You are the man!
n33x