tags:

views:

796

answers:

4

In jQuery, is it possible to invoke a callback or trigger an event after an invocation of .each() (or any other type of iterative callback) has completed.

For example, I would like this "fade and remove" to complete

$(parentSelect).nextAll().fadeOut(200, function() {
    $(this).remove();
});

before doing some calculations and inserting new elements after the $(parentSelect). My calculations are incorrect if the existing elements are still visible to jQuery and sleeping/delaying some arbitrary amount of time (200 for each element) seems like a brittle solution at best.

I can easily .bind() the necessary logic to an event callback but I'm not sure how to cleanly invoke the .trigger() after the above iteration has completed. Obviously, I can't invoke the trigger inside the iteration as it would fire multiple times.

In the case of $.each(), I've considered adding something to the end of the data argument (that I'd manually look for in the iteration body) but I'd hate to be forced to that so I was hoping there was some other elegant way to control the flow with respect to iterative callbacks.

A: 

If you're willing to make it a couple of steps, this might work. It's dependent on the animations finishing in order, though. I don't think that should be a problem.

var elems = $(parentSelect).nextAll();
var lastID = elems.length - 1;

elems.each( function(i) {
    $(this).fadeOut(200, function() { 
        $(this).remove(); 
        if (i == lastID) {
           doMyThing();
        }
    });
});
tvanfosson
This would work - although, not what I'd hoped. Naively, I'm looking for something like elems.each(foo,bar) where foo='function applied to each element' and bar='callback after all iterations have completed'. I'll give the question a bit more time to see if we come across some dark corner of jQuery :)
Luther Baker
AFAIK - you have to trigger it when the "last" animation completes and since they run asynchronously, you'd have to do it in the animation callback. I like @Pointy's version of what I wrote better since it doesn't have the dependency on the ordering, not that I think that would be a problem.
tvanfosson
+4  A: 

An alternative to @tv's answer:

var elems = $(parentSelect).nextAll(), count = elems.length;

elems.each( function(i) {
  $(this).fadeOut(200, function() { 
    $(this).remove(); 
    if (!--count) doMyThing();
  });
});
Pointy
I like this change, though I'd make the comparison to 0 instead of the logical negation (--count == 0) since you're counting down. To me it makes the intent clearer, though it has the same effect.
tvanfosson
Sorry @tv, I'm an inveterate C programmer :-)
Pointy
As it turns out, your initial comment on the question was spot on. I was asking about .each() and thought that is what I wanted but as you and tvanfosson and now patrick have pointed out - it was the final fadeOut that I was actually interested in. I think we all agree that your example (counting instead of indexes) is probably the safest.
Luther Baker
+1  A: 

Javascript runs synchronously, so whatever you place after each() will not run until each() is complete.

Consider the following test:

    var count = 0;
    var array = [];

    // populate an array with 1,000,000 entries
    for(var i = 0; i < 1000000; i++) {
        array.push(i);
    }

    // use each to iterate over the array, incrementing count each time
    $.each(array, function() {
        count++
    });

    // the alert won't get called until the 'each' is done
    //      as evidenced by the value of count
    alert(count);

When the alert is called, count will equal 1000000 because the alert won't run until each() is done.

patrick dw
The problem in the example given is that the fadeOut gets put on the animation queue and doesn't execute synchronously. If you use an `each` and schedule each animation separately, you still have to fire the event after the animation, not the each finishes.
tvanfosson
@tvanfosson - Yeah, I know. According to what Luther wants to accomplish, your solution (and Pointy's) seems like the right approach. But from Luther's comments like... **Naively, I'm looking for something like elems.each(foo,bar) where foo='function applied to each element' and bar='callback after all iterations have completed'.** ...he seems insistent that it is an `each()` issue. That's why I threw this answer into the mix.
patrick dw
You are correct Patrick. I did (mis)understand that .each() was scheduling or queuing my callbacks. I think that invoking fadeOut was indirectly leading me to that conclusion. tvanfosson and Pointy have both provided answers for the fadeOut problem (which is what I was really after) but your post actually corrects my understanding a bit. Your answer actually best answers the original question as stated while Pointy and tvanfosson answered the question I was trying to ask. I wish I could pick two answers. Thanks for 'throwing this answer into the mix' :)
Luther Baker
Glad you got things working!
patrick dw
A: 

what about

$(parentSelect).nextAll().fadeOut(200, function() { 
    $(this).remove(); 
}).one(function(){
    myfunction();
}); 
Mark Schultheiss
This definitely invokes myfunction() once (and introduces me to another jQuery concept) but what I was looking for was a guarantee of 'when' it would run - not merely that it would run once. And, as Patrick mentions, that part turns out to be pretty easy. What I was really looking for was a way to invoke something after the last fadeOut logic ran which, I don't think .one() guarantees.
Luther Baker
I think that the chain does that - since the first part runs before the last part.
Mark Schultheiss