2010-03-01 4 views
153

In jQuery abgeschlossen ist, ist es möglich, einen Rückruf aufrufe oder Trigger ein Ereignis nach einem Aufruf von .each() (oder jeder anderen Art von iterativem Rückruf) abgeschlossen .eine jQuery Funktion nach .each() aufgerufen hat

Zum Beispiel würde Ich mag diese „fade und entfernen“

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

vor einigen Berechnungen zu tun und das Einfügen neue Elemente nach den $(parentSelect) abzuschließen. Meine Berechnungen sind inkorrekt, wenn die vorhandenen Elemente für jQuery noch sichtbar sind und das Einschlafen/Verzögern um eine willkürliche Zeitspanne (200 für jedes Element) bestenfalls als brüchige Lösung erscheint.

Ich kann leicht .bind() die notwendige Logik, um eine Callback-Ereignis, aber ich bin nicht sicher, wie sauber die .trigger() nach der obigen Iteration aufgerufen hat abgeschlossen. Offensichtlich kann ich den Trigger innerhalb der Iteration nicht aufrufen, da er mehrmals ausgelöst würde.

Im Fall $.each() habe ich darüber nachgedacht, etwas zum Ende des Datenarguments hinzuzufügen (das würde ich manuell im Iterationskörper suchen), aber ich würde es hassen gezwungen zu sein, so dass ich gehofft hatte Es gab eine andere elegante Möglichkeit, den Fluss in Bezug auf iterative Rückrufe zu steuern.

+1

Verstehe ich richtig, dass es nicht so sehr die ".each()" selbst ist, die Sie beenden möchten, sondern alle Animationen, die durch den Aufruf ".each()" gestartet werden? – Pointy

+0

Das ist ein guter Punkt. Mit dieser speziellen Frage, ja, bin ich hauptsächlich besorgt über die Fertigstellung von ".each()" selbst ... aber ich denke, Sie werfen eine andere tragfähige Frage auf. –

Antwort

140

Eine Alternative zur Antwort des @ tv:

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

elems.each(function(i) { 
    $(this).fadeOut(200, function() { 
    $(this).remove(); 
    if (!--count) doMyThing(); 
    }); 
}); 

Beachten Sie, dass .each() selbst Synchron — die Anweisung, die den Aufruf von .each() nur dann ausgeführt wird folgt, ist nach dem .each() Aufruf abgeschlossen ist. Asynchrone Operationen gestartet in der .each() Iteration wird natürlich auf ihre eigene Art und Weise weiter. Das ist das Problem: Die Aufrufe zum Ausblenden der Elemente sind zeitgesteuerte Animationen, und diese werden in ihrem eigenen Tempo fortgesetzt.

Die obige Lösung verfolgt daher, wie viele Elemente ausgeblendet werden. Jeder Anruf an .fadeOut() ruft einen Abschlussrückruf ab. Wenn der Rückruf merkt, dass er durch alle beteiligten Originalelemente gezählt wird, kann eine nachfolgende Aktion mit der Gewissheit ausgeführt werden, dass das gesamte Überblenden beendet ist.

Dies ist eine vier Jahre alte Antwort (zu diesem Zeitpunkt im Jahr 2014). Ein moderner Weg, dies zu tun, würde wahrscheinlich den Deferred/Promise-Mechanismus beinhalten, obwohl das obige einfach ist und gut funktionieren sollte.

+4

Ich mag diese Änderung, obwohl ich den Vergleich mit 0 anstelle der logischen Negation (--count == 0) machen würde, da Sie rückwärts zählen. Für mich macht es die Absicht klarer, obwohl es die gleiche Wirkung hat. – tvanfosson

+0

Wie sich herausstellte, war Ihr erster Kommentar zu der Frage genau richtig. Ich fragte nach .each() und dachte, das ist was ich wollte, aber wie du und tvanfosson und jetzt patrick darauf hingewiesen haben - es war das letzte fadeOut, an dem ich eigentlich interessiert war. Ich denke, wir sind uns alle einig, dass dein Beispiel (anstatt zu zählen) von Indizes) ist wahrscheinlich der sicherste. –

+0

@ A.DIMO Was meinst du mit "zu kompliziert"? Welcher Teil ist kompliziert? – Pointy

1

Wenn Sie bereit sind, es ein paar Schritte zu machen, könnte dies funktionieren. Es hängt jedoch von der Reihenfolge der Animationen ab. Ich denke nicht, dass das ein Problem sein sollte.

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

elems.each(function(i) { 
    $(this).fadeOut(200, function() { 
     $(this).remove(); 
     if (i == lastID) { 
      doMyThing(); 
     } 
    }); 
}); 
+0

Das würde funktionieren - allerdings nicht, was ich mir erhofft hatte. Naiv suche ich nach etwas wie elems.each (foo, bar), wobei foo = 'function für jedes Element' und bar = 'callback, nachdem alle Iterationen abgeschlossen sind'. Ich werde die Frage ein wenig mehr Zeit geben, um zu sehen, ob wir über eine dunkle Ecke von jQuery kommen :) –

+0

AFAIK - Sie müssen es auslösen, wenn die "letzte" Animation abgeschlossen ist und da sie asynchron laufen, müssten Sie tun es in der Animation Rückruf. Ich mag @ Pointys Version dessen, was ich besser geschrieben habe, da es nicht die Abhängigkeit von der Reihenfolge hat, nicht dass ich denke, dass das ein Problem wäre. – tvanfosson

19

JavaScript wird synchron ausgeführt. Sie können also nach each() keine Daten eingeben, bis each() abgeschlossen ist.

Betrachten Sie den folgenden 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); 

Wenn der Alarm genannt wird, Zahl 1000000 gleich, weil der Alarm wird erst ausgeführt, wenn each() erfolgt.

+4

Das Problem im angegebenen Beispiel ist, dass fadeOut in die Animationswarteschlange gestellt wird und nicht synchron ausgeführt wird. Wenn Sie ein "each" verwenden und jede Animation separat planen, müssen Sie das Ereignis nach der Animation noch auslösen, nicht jedes Ende. – tvanfosson

+3

@tvanfosson - Ja, ich weiß. Nach dem, was Luther erreichen möchte, scheint Ihre Lösung (und die von Pointy) der richtige Ansatz zu sein. Aber aus Luthers Kommentaren wie ... ** Naiv suche ich nach etwas wie elems.each (foo, bar) wo foo = 'funktion angewendet auf jedes element' und bar = 'callback nachdem alle Iterationen abgeschlossen sind'. * * ... er scheint darauf zu bestehen, dass es sich um ein 'jedes()' Problem handelt. Deshalb habe ich diese Antwort in die Mischung geworfen. – user113716

+0

Sie sind richtig Patrick. Ich habe (falsch) verstanden, dass .each() meine Rückrufe einplante oder einreihte. Ich denke, dass der Aufruf von fadeOut mich indirekt zu dieser Schlussfolgerung führte. tvanfosson und Pointy haben beide Antworten auf das fadeOut-Problem gegeben (was ich wirklich wollte), aber Ihr Post korrigiert mein Verständnis ein bisschen. Ihre Antwort beantwortet die ursprüngliche Frage am besten, während Pointy und Tvanfosson die Frage beantworteten, die ich zu stellen versuchte. Ich wünschte, ich könnte zwei Antworten auswählen. Danke, dass du diese Antwort in die Mischung geworfen hast :) –

0

was

$(parentSelect).nextAll().fadeOut(200, function() { 
    $(this).remove(); 
}).one(function(){ 
    myfunction(); 
}); 
+0

Dies ruft definitiv einmal myfunction() auf (und führt mich in ein anderes jQuery-Konzept ein), aber was ich suchte, war eine Garantie dafür, "wann" es laufen würde - nicht nur, dass es einmal laufen würde. Und wie Patrick erwähnt, ist dieser Teil ziemlich einfach. Was ich wirklich suchte, war eine Möglichkeit, etwas aufzurufen, nachdem die letzte fadeOut-Logik ausgeführt wurde, die, glaube ich, keine .one() garantiert. –

+0

Ich denke, dass die Kette das macht - da der erste Teil vor dem letzten Teil läuft. –

0

Sie haben den Rest Ihrer Anfrage in die Warteschlange für sie zu arbeiten.

5

Ich habe eine Menge Antworten gefunden, die sich mit Arrays befassen, aber nicht mit einem JSON-Objekt. Meine Lösung bestand einfach darin, das Objekt einmal zu durchlaufen, während ein Zähler inkrementiert wurde, und dann, wenn ich durch das Objekt iterierte, um Ihren Code auszuführen, können Sie einen zweiten Zähler inkrementieren. Dann vergleichen Sie einfach die beiden Zähler miteinander und erhalten Ihre Lösung. Ich weiß, dass es etwas klobig ist, aber ich habe bisher keine elegantere Lösung gefunden. Das ist mein Beispielcode:

var flag1 = flag2 = 0; 

$.each(object, function (i, v) { flag1++; }); 

$.each(object, function (ky, val) { 

    /* 
     Your code here 
    */ 
    flag2++; 
}); 

if(flag1 === flag2) { 
    your function to call at the end of the iteration 
} 

Wie ich schon sagte, es ist nicht das eleganteste, aber es funktioniert und es funktioniert gut und ich habe nicht eine bessere Lösung nur noch nicht gefunden.

Cheers, JP

145

Ok, das könnte ein wenig nach der Tat, aber .promise() sollte auch das erreichen, was Sie wollen.

Promise documentation

Ein Beispiel aus einem Projekt arbeiten, ich bin:

$('.panel') 
    .fadeOut('slow') 
    .promise() 
    .done(function() { 
     $('#' + target_panel).fadeIn('slow', function() {}); 
    }); 

:)

141

Es ist wahrscheinlich zu spät, aber ich denke, diese Code Arbeit ...

$blocks.each(function(i, elm) { 
$(elm).fadeOut(200, function() { 
    $(elm).remove(); 
}); 
}).promise().done(function(){ alert("All was done"); }); 
0

Ich treffe das gleiche Problem und ich löste mit einer Lösung wie die folgenden Kabeljau e:

var drfs = new Array(); 
var external = $.Deferred(); 
drfs.push(external.promise()); 

$('itemSelector').each(function() { 
    //initialize the context for each cycle 
    var t = this; // optional 
    var internal = $.Deferred(); 

    // after the previous deferred operation has been resolved 
    drfs.pop().then(function() { 

     // do stuff of the cycle, optionally using t as this 
     var result; //boolean set by the stuff 

     if (result) { 
      internal.resolve(); 
     } else { 
      internal.reject(); 
     } 
    } 
    drfs.push(internal.promise()); 
}); 

external.resolve("done"); 

$.when(drfs).then(function() { 
    // after all each are resolved 

}); 

Die Lösung löst das folgende Problem: die asynchronen Operationen begann im .each() Iteration unter Verwendung der latenten Objekt zu synchronisieren.