]> source.dussan.org Git - gwtquery.git/commitdiff
Implement promises for QueuePlugin (valid for GQuery and Effects). Fix delay method...
authorManuel Carrasco Moñino <manuel.carrasco.m@gmail.com>
Tue, 2 Apr 2013 18:29:52 +0000 (20:29 +0200)
committerManuel Carrasco Moñino <manuel.carrasco.m@gmail.com>
Tue, 2 Apr 2013 18:29:52 +0000 (20:29 +0200)
gwtquery-core/src/main/java/com/google/gwt/query/client/Function.java
gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java
gwtquery-core/src/main/java/com/google/gwt/query/client/LazyGQuery.java
gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/QueuePlugin.java
gwtquery-core/src/main/java/com/google/gwt/query/client/plugins/deferred/Deferred.java
gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryDeferredTestGwt.java

index de53806bd9b88910172ca9fc958a58324f23920f..7e636a72fe224549312843d8822765a5be92c33c 100644 (file)
@@ -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) {
index 36a6647f080dd1273648cbdee51f8f7f9de8e8a8..54238066fe061f36512545e469b7c7f595b69646 100644 (file)
@@ -710,15 +710,23 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
   }
   
   /**
-   * 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<GQuery, LazyGQuery> {
     }
     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.
    *
index 6b5113e29e0e96919b054f0d5767ca0e74e4759e..b500f556d5eab74c24ddf9e5b62d1839f663b7f9 100644 (file)
  * 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<T> extends LazyBase<T>{
 
@@ -1781,6 +1744,18 @@ public interface LazyGQuery<T> extends LazyBase<T>{
    */
   LazyGQuery<T> 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.
    *
index cee848da692dca1aadf283e3b6a5cc86621a64e0..ddd9763be4d790d4e6afc44ea0233a1a025eb077 100644 (file)
  */
 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<T extends 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<T extends 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<T extends 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<T extends 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<T extends QueuePlugin<?>> extends GQuery {
    * in the named queue.
    */
   public int queue(String name) {
-    Queue<Object> 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<T extends QueuePlugin<?>> extends GQuery {
   }
 
   private void dequeueCurrentAndRunNext(Element elem, String name) {
-    Queue<?> q = queue(elem, name, null);
+    Queue<? extends Function> 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<? extends Function> 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 <S> Queue<S> queue(Element elem, String name, S func) {
+  protected <S extends Function> Queue<S> queue(Element elem, String name, S func) {
     if (elem != null) {
       Queue<S> q = (Queue<S>) data(elem, name, null);
       if (func != null) {
@@ -282,9 +328,7 @@ public class QueuePlugin<T extends 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<T extends 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<T extends 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);
index c36284f1fbad7122cc9cb13be8f50583f64b98d4..40df7dbff0e280f4b7359a4ec7449522a57c1798 100644 (file)
@@ -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) {
index bcfe3ea60cc58d008638734deee90a7d7aec133f..c063a65db8fe7a54554526b3f3bb824ce907ae24 100644 (file)
 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);
+    
+    $("<div>a1</div><div>a2</div>")
+      .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("<button>click</button><p>Ready...</p><br/><div></div>");
+    $("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("<button>click</button><p>Ready...</p><br/><div></div><div></div><div></div><div></div>");
+    $("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();
+  }  
 }