From 9d0eb980fcbcd3ecd1c3f73cb3d2882b9cc15604 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Manuel=20Carrasco=20Mo=C3=B1ino?= Date: Tue, 2 Apr 2013 20:29:52 +0200 Subject: [PATCH] Implement promises for QueuePlugin (valid for GQuery and Effects). Fix delay method in QueuePlugin which was executing functions twice, and fix dequeue as well. Added tests --- .../com/google/gwt/query/client/Function.java | 2 +- .../com/google/gwt/query/client/GQuery.java | 32 +++- .../google/gwt/query/client/LazyGQuery.java | 53 ++----- .../gwt/query/client/plugins/QueuePlugin.java | 109 ++++++++++---- .../client/plugins/deferred/Deferred.java | 27 +++- .../query/client/GQueryDeferredTestGwt.java | 141 +++++++++++++++++- 6 files changed, 290 insertions(+), 74 deletions(-) diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/Function.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/Function.java index de53806b..7e636a72 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/Function.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/Function.java @@ -410,7 +410,7 @@ public abstract class Function { * Override this for GQuery methods which take a callback and do not expect a * return value. * - * @param e takes a com.google.gwt.user.client.Element + * @param elem takes a com.google.gwt.user.client.Element */ private boolean loop = false; public void f(com.google.gwt.user.client.Element e) { diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java index 36a6647f..54238066 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java @@ -710,15 +710,23 @@ public class GQuery implements Lazy { } /** - * Provides a way to execute callback Functions based on one or more Promise objects + * Provides a way to execute callback Functions based on one or more objects * that represent asynchronous events. * - * Returns a new promise which will be finalized when all of its subordinates finish. + * Arguments can be of any Object, but normally you would pass Promises. + * In the case you provide a GQuery object it will call the promise() method to return + * a Promise which will be executed when the queue is resolved. + * In the case you provide a normal Object, it will return a promise which will be immediately + * resolved with the object as argument. + * In the case you provide a Function it will executed and if the f(Object...) method returns + * a new promise it will be used, otherwise we will use the returned object like in the last case. + * + * It Returns a new promise which will be finalized when all of its subordinates finish. * In the case of all subordinates are resolved correctly the promise will be resolved * otherwise it will be rejected. * */ - public static Promise when(Promise... subordinates) { + public static Promise when(Object... subordinates) { return Deferred.when(subordinates); } @@ -3480,7 +3488,23 @@ public class GQuery implements Lazy { } return pushStack(unique(result), "prevUntil", getSelector()); } - + + /** + * Returns a dynamically generated Promise that is resolved once all actions + * in the queue have ended. + */ + public Promise promise() { + return as(Queue).promise(); + } + + /** + * Returns a dynamically generated Promise that is resolved once all actions + * in the named queue have ended. + */ + public Promise promise(String name) { + return as(Queue).promise(name); + } + /** * Get the value of a property for the first element in the set of matched elements. * diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/LazyGQuery.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/LazyGQuery.java index 6b5113e2..b500f556 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/LazyGQuery.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/LazyGQuery.java @@ -12,60 +12,23 @@ * the License. */ package com.google.gwt.query.client; -import static com.google.gwt.query.client.plugins.QueuePlugin.Queue; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArray; -import com.google.gwt.core.client.JsArrayMixed; -import com.google.gwt.core.client.JsArrayString; -import com.google.gwt.dom.client.BodyElement; -import com.google.gwt.dom.client.ButtonElement; -import com.google.gwt.dom.client.Document; + import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.InputElement; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.OptionElement; -import com.google.gwt.dom.client.SelectElement; -import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.HasCssName; -import com.google.gwt.dom.client.TextAreaElement; +import com.google.gwt.query.client.GQuery.Offset; import com.google.gwt.query.client.css.CSS; import com.google.gwt.query.client.css.HasCssValue; import com.google.gwt.query.client.css.TakesCssValue; import com.google.gwt.query.client.css.TakesCssValue.CssSetter; -import com.google.gwt.query.client.impl.AttributeImpl; -import com.google.gwt.query.client.impl.DocumentStyleImpl; -import com.google.gwt.query.client.impl.SelectorEngine; -import com.google.gwt.query.client.js.JsCache; -import com.google.gwt.query.client.js.JsMap; import com.google.gwt.query.client.js.JsNamedArray; import com.google.gwt.query.client.js.JsNodeArray; -import com.google.gwt.query.client.js.JsObjectArray; -import com.google.gwt.query.client.js.JsRegexp; -import com.google.gwt.query.client.js.JsUtils; import com.google.gwt.query.client.plugins.Effects; -import com.google.gwt.query.client.plugins.Events; -import com.google.gwt.query.client.plugins.Plugin; -import com.google.gwt.query.client.plugins.Widgets; -import com.google.gwt.query.client.plugins.ajax.Ajax; -import com.google.gwt.query.client.plugins.ajax.Ajax.Settings; -import com.google.gwt.query.client.plugins.deferred.Deferred; import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.Easing; -import com.google.gwt.query.client.plugins.events.EventsListener; -import com.google.gwt.query.client.plugins.widgets.WidgetsUtils; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.EventListener; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.query.client.GQuery.Offset; -import com.google.gwt.query.client.LazyBase; public interface LazyGQuery extends LazyBase{ @@ -1781,6 +1744,18 @@ public interface LazyGQuery extends LazyBase{ */ LazyGQuery prevUntil(GQuery until, String filter); + /** + * Returns a dynamically generated Promise that is resolved once all actions + * in the queue have ended. + */ + Promise promise(); + + /** + * Returns a dynamically generated Promise that is resolved once all actions + * in the named queue have ended. + */ + Promise promise(String name); + /** * Get the value of a property for the first element in the set of matched elements. * diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/QueuePlugin.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/QueuePlugin.java index cee848da..ddd9763b 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/QueuePlugin.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/QueuePlugin.java @@ -15,14 +15,16 @@ */ package com.google.gwt.query.client.plugins; +import java.util.LinkedList; +import java.util.Queue; + import com.google.gwt.dom.client.Element; import com.google.gwt.query.client.Function; import com.google.gwt.query.client.GQuery; +import com.google.gwt.query.client.Promise; +import com.google.gwt.query.client.plugins.deferred.Callbacks; import com.google.gwt.user.client.Timer; -import java.util.LinkedList; -import java.util.Queue; - /** * Class used in plugins which need a queue system. */ @@ -36,24 +38,28 @@ public class QueuePlugin> extends GQuery { } }); + /** + * Function used to delay the execution of a set of functions in + * a queue. + */ protected class DelayFunction extends Function { private class SimpleTimer extends Timer { public void run() { - g.each(funcs); - for (Element e: g.elements()) { - dequeueIfNotDoneYet(e, name, DelayFunction.this); + for (Function f : funcs) { + f.fe(elem); } + dequeueIfNotDoneYet(elem, name, DelayFunction.this); } } private int delay; Function[] funcs; - GQuery g; + Element elem; String name; - public DelayFunction(GQuery gquery, String name, int delayInMilliseconds, Function... f) { - this.g = gquery; - this.delay = delayInMilliseconds; + public DelayFunction(Element elem, String name, int delay, Function... f) { + this.elem = elem; + this.delay = delay; this.funcs = f; this.name = name; } @@ -67,6 +73,7 @@ public class QueuePlugin> extends GQuery { public static final String JUMP_TO_END = QueuePlugin.class.getName() + ".StopData"; protected static final String QUEUE_DATA_PREFIX = QueuePlugin.class.getName() + ".Queue-"; protected static String DEFAULT_NAME = QUEUE_DATA_PREFIX + "fx"; + private static final String EMPTY_HOOKS = ".Empty"; protected QueuePlugin(GQuery gq) { super(gq); @@ -102,8 +109,10 @@ public class QueuePlugin> extends GQuery { */ @SuppressWarnings("unchecked") public T delay(int milliseconds, String name, Function... funcs) { - queue(name, new DelayFunction(this, name, milliseconds, funcs)); - return (T) this; + for (Element e : elements()) { + queue(e, name, new DelayFunction(e, name, milliseconds, funcs)); + } + return (T)this; } /** @@ -124,6 +133,38 @@ public class QueuePlugin> extends GQuery { return (T) this; } + /** + * Returns a dynamically generated Promise that is resolved once all actions + * in the queue have ended. + */ + public Promise promise() { + return promise(DEFAULT_NAME); + } + + /** + * Returns a dynamically generated Promise that is resolved once all actions + * in the named queue have ended. + */ + public Promise promise(String name) { + final Promise.Deferred dfd = Deferred(); + + Function resolve = new Function() { + int l = QueuePlugin.this.length(); + public Object f(Object... args) { + if (--l == 0) { + dfd.resolve(); + } + return null; + } + }; + + for (Element elem: elements()) { + emptyHooks(elem, name).add(resolve); + } + + return dfd.promise(); + } + /** * Show the number of functions to be executed on the first matched element * in the effects queue. @@ -137,7 +178,7 @@ public class QueuePlugin> extends GQuery { * in the named queue. */ public int queue(String name) { - Queue q = isEmpty() ? null : queue(get(0), name, null); + Queue q = isEmpty() ? null : queue(get(0), name, null); return q == null? 0 : q.size(); } @@ -255,25 +296,30 @@ public class QueuePlugin> extends GQuery { } private void dequeueCurrentAndRunNext(Element elem, String name) { - Queue q = queue(elem, name, null); + Queue q = queue(elem, name, null); if (q != null) { // Remove current function q.poll(); // Run the next in the queue - Object f = q.peek(); - if (f != null) { - if (f instanceof Function) { - ((Function) f).fe(elem); - } - } else { - // if it is the last function remove the queue to avoid leaks (issue 132) - removeData(elem, name); - } + runNext(elem, name, q); + } + } + + private void runNext(Element elem, String name, Queue q) { + assert q != null; + Function f = q.peek(); + if (f != null) { + f.fe(elem); + } else { + // Run final hooks when emptying the queue, used in promises + emptyHooks(elem, name).fire(elem, name, f); + // It is the last function, remove the queue to avoid leaks (issue 132) + removeData(elem, name); } } @SuppressWarnings("unchecked") - protected Queue queue(Element elem, String name, S func) { + protected Queue queue(Element elem, String name, S func) { if (elem != null) { Queue q = (Queue) data(elem, name, null); if (func != null) { @@ -282,9 +328,7 @@ public class QueuePlugin> extends GQuery { } q.add(func); if (q.size() == 1) { - if (func instanceof Function) { - ((Function) func).fe(elem); - } + runNext(elem, name, q); } } return q; @@ -299,7 +343,7 @@ public class QueuePlugin> extends GQuery { public void dequeueIfNotDoneYet(Element elem, String name, Object object) { Queue queue = queue(elem, name, null); if (queue != null && object.equals(queue.peek())) { - dequeue(name); + dequeueCurrentAndRunNext(elem, name); } } @@ -308,6 +352,15 @@ public class QueuePlugin> extends GQuery { data(elem, name, queue); } } + + private Callbacks emptyHooks(Element elem, String name) { + String key = name + EMPTY_HOOKS; + Callbacks c = (Callbacks)data(elem, key, null); + if (c == null) { + c = (Callbacks)data(elem, key, new Callbacks("once memory")); + } + return c; + } private void stop(Element elem, String name, boolean clear, boolean jumpToEnd) { Queue q = queue(elem, name, null); diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/deferred/Deferred.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/deferred/Deferred.java index c36284f1..40df7dbf 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/deferred/Deferred.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/deferred/Deferred.java @@ -197,7 +197,32 @@ public class Deferred extends GQuery implements Promise.Deferred { return new Deferred(gq); } }); - + + public static Promise when(Object... d) { + int l = d.length; + Promise[] p = new Promise[l]; + for (int i = 0; i < l; i++) { + p[i] = makePromise(d[i]); + } + return when(p); + } + + private static Promise makePromise(final Object o) { + if (o instanceof Promise) { + return (Promise)o; + } else if (o instanceof Function) { + return makePromise(((Function)o).f(new Object[0])); + } else if (o instanceof GQuery) { + return ((GQuery)o).promise(); + } else { + return new PromiseFunction() { + public void f(Deferred dfd) { + dfd.resolve(o); + } + }; + } + } + public static Promise when(Promise... d) { final int n = d.length; switch (n) { diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryDeferredTestGwt.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryDeferredTestGwt.java index bcfe3ea6..c063a65d 100644 --- a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryDeferredTestGwt.java +++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryDeferredTestGwt.java @@ -16,24 +16,51 @@ package com.google.gwt.query.client; +import static com.google.gwt.query.client.GQuery.$; +import static com.google.gwt.query.client.GQuery.$$; +import static com.google.gwt.query.client.GQuery.document; + +import com.google.gwt.core.client.Duration; import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.query.client.plugins.ajax.Ajax; import com.google.gwt.query.client.plugins.deferred.Callbacks; import com.google.gwt.query.client.plugins.deferred.Callbacks.Callback; import com.google.gwt.query.client.plugins.deferred.PromiseFunction; +import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.RootPanel; /** * Test class for testing deferred and callbacks stuff. */ public class GQueryDeferredTestGwt extends GWTTestCase { + + static Element e = null; + + static HTML testPanel = null; public String getModuleName() { return "com.google.gwt.query.Query"; } - private String result = ""; + public void gwtTearDown() { + $(e).remove(); + e = null; + } + public void gwtSetUp() { + if (e == null) { + testPanel = new HTML(); + RootPanel.get().add(testPanel); + e = testPanel.getElement(); + e.setId("core-tst"); + } else { + e.setInnerHTML(""); + } + } + + private String result = ""; public void testCallbacks() { Function fn1 = new Function() { public Object f(Object...arguments) { @@ -299,5 +326,117 @@ public class GQueryDeferredTestGwt extends GWTTestCase { } }); } + + public void testDeferredQueueDelay() { + final int delay = 300; + final double init = Duration.currentTimeMillis(); + + delayTestFinish(delay * 2); + + Function doneFc = new Function() { + public void f() { + finishTest(); + double ellapsed = Duration.currentTimeMillis() - init; + assertTrue(ellapsed >= delay); + } + }; + + $(document).delay(delay).promise().done(doneFc); + } + + int deferredRun = 0; + public void testDeferredQueueMultipleDelay() { + final int delay = 300; + final double init = Duration.currentTimeMillis(); + deferredRun = 0; + + delayTestFinish(delay * 3); + + $("
a1
a2
") + .delay(delay, new Function() { + public void f() { + double ellapsed = Duration.currentTimeMillis() - init; + assertTrue(ellapsed >= delay); + deferredRun ++; + } + }) + .delay(delay, new Function() { + public void f() { + double ellapsed = Duration.currentTimeMillis() - init; + assertTrue(ellapsed >= (delay * 2)); + deferredRun ++; + } + }) + .promise().done(new Function() { + public void f() { + finishTest(); + // Functions are run 4 times (2 functions * 2 elements) + assertEquals(4, deferredRun); + } + }); + } + + /** + * Example taken from the gquery.promise() documentation + */ + public void testDeferredEffect() { + $(e).html("

Ready...


"); + $("div", e).css($$("height: 50px; width: 50px;float: left; margin-right: 10px;display: none; background-color: #090;")); + + final Function effect = new Function() {public Object f(Object... args) { + return $("div", e).fadeIn(800).delay(1200).fadeOut(); + }}; + + final double init = Duration.currentTimeMillis(); + + delayTestFinish(10000); + + $("button", e) + .click(new Function(){public void f() { + $("p", e).append(" Started... "); + GQuery.when( effect ).done(new Function(){public void f() { + $("p", e).append(" Finished! "); + assertEquals("Ready... Started... Finished! ", $("p", e).text()); + + double ellapsed = Duration.currentTimeMillis() - init; + assertTrue(ellapsed >= (800 + 1200 + 400)); + + finishTest(); + }}); + }}) + .click(); + } + + /** + * Example taken from the gquery.promise() documentation + */ + public void testDeferredEffectEach() { + $(e).html("

Ready...


"); + $("div", e).css($$("height: 50px; width: 50px;float: left; margin-right: 10px;display: none; background-color: #090;")); + + final double init = Duration.currentTimeMillis(); + + delayTestFinish(10000); + + $("button", e) + .bind("click", new Function(){public void f() { + $("p", e).append(" Started... "); + + $("div",e).each(new Function(){public Object f(Element e, int i) { + return $( this ).fadeIn().fadeOut( 1000 * (i+1) ); + }}); + + $("div", e).promise().done(new Function(){ public void f() { + $("p", e).append( " Finished! " ); + + assertEquals("Ready... Started... Finished! ", $("p", e).text()); + double ellapsed = Duration.currentTimeMillis() - init; + assertTrue(ellapsed >= (1000 * 4)); + + finishTest(); + }}); + }}) + .click(); + } } -- 2.39.5