Notifies property changes in one batch batchnotify
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 12 Sep 2013 17:59:10 +0200
branchbatchnotify
changeset 2947a3abb6bcfc9
parent 293 79fc3a8e1a6e
child 295 7c422ac65622
Notifies property changes in one batch
boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java
ko-fx/src/main/java/org/apidesign/html/kofx/FXContext.java
ko-fx/src/main/java/org/apidesign/html/kofx/Knockout.java
ko-fx/src/test/java/org/apidesign/html/kofx/LessCallbacksCheck.java
     1.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Thu Sep 12 17:18:18 2013 +0200
     1.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Thu Sep 12 17:59:10 2013 +0200
     1.3 @@ -36,6 +36,7 @@
     1.4   */
     1.5  public final class FnUtils {
     1.6      private static final ThreadLocal<Fn.Presenter> CURRENT = new ThreadLocal<Fn.Presenter>();
     1.7 +    private static final ThreadLocal<List<Runnable>> LATER = new ThreadLocal<List<Runnable>>();
     1.8      
     1.9      private FnUtils() {
    1.10      }
    1.11 @@ -130,8 +131,28 @@
    1.12      }
    1.13      
    1.14      public static Fn.Presenter currentPresenter(Fn.Presenter p) {
    1.15 +        List<Runnable> list = LATER.get();
    1.16 +        LATER.set(null);
    1.17 +        if (list != null) for (Runnable runnable : list) {
    1.18 +            try {
    1.19 +                runnable.run();
    1.20 +            } catch (Throwable t) {
    1.21 +                t.printStackTrace();
    1.22 +            }
    1.23 +        }
    1.24 +        
    1.25          Fn.Presenter prev = CURRENT.get();
    1.26          CURRENT.set(p);
    1.27          return prev;
    1.28      }
    1.29 +    
    1.30 +    public static void runLater(Runnable r) {
    1.31 +        currentPresenter();
    1.32 +        List<Runnable> list = LATER.get();
    1.33 +        if (list == null) {
    1.34 +            list = new ArrayList<Runnable>();
    1.35 +            LATER.set(list);
    1.36 +        }
    1.37 +        list.add(r);
    1.38 +    }
    1.39  }
     2.1 --- a/ko-fx/src/main/java/org/apidesign/html/kofx/FXContext.java	Thu Sep 12 17:18:18 2013 +0200
     2.2 +++ b/ko-fx/src/main/java/org/apidesign/html/kofx/FXContext.java	Thu Sep 12 17:59:10 2013 +0200
     2.3 @@ -22,7 +22,13 @@
     2.4  
     2.5  import java.io.IOException;
     2.6  import java.io.InputStream;
     2.7 +import java.util.ArrayList;
     2.8 +import java.util.HashMap;
     2.9 +import java.util.HashSet;
    2.10 +import java.util.List;
    2.11 +import java.util.Map;
    2.12  import java.util.ServiceLoader;
    2.13 +import java.util.Set;
    2.14  import java.util.logging.Logger;
    2.15  import javafx.application.Platform;
    2.16  import net.java.html.js.JavaScriptBody;
    2.17 @@ -47,10 +53,12 @@
    2.18   * @author Jaroslav Tulach <jtulach@netbeans.org>
    2.19   */
    2.20  public final class FXContext
    2.21 -implements Technology.BatchInit<JSObject>, Transfer, WSTransfer<LoadWS> {
    2.22 +implements Technology.BatchInit<JSObject>, Transfer, WSTransfer<LoadWS>, Runnable {
    2.23      static final Logger LOG = Logger.getLogger(FXContext.class.getName());
    2.24      private static Boolean javaScriptEnabled;
    2.25      private final Fn.Presenter browserContext;
    2.26 +    
    2.27 +    private final Map<JSObject,Set<String>> notifyMutated = new HashMap<JSObject, Set<String>>();
    2.28  
    2.29      public FXContext(Fn.Presenter browserContext) {
    2.30          this.browserContext = browserContext;
    2.31 @@ -105,8 +113,16 @@
    2.32      }
    2.33  
    2.34      @Override
    2.35 -    public void valueHasMutated(JSObject data, String propertyName) {
    2.36 -        Knockout.valueHasMutated(data, propertyName);
    2.37 +    public synchronized void valueHasMutated(JSObject data, String propertyName) {
    2.38 +        if (notifyMutated.isEmpty()) {
    2.39 +            FnUtils.runLater(this);
    2.40 +        }
    2.41 +        Set<String> ss = notifyMutated.get(data);
    2.42 +        if (ss == null) {
    2.43 +            ss = new HashSet<String>();
    2.44 +            notifyMutated.put(data, ss);
    2.45 +        }
    2.46 +        ss.add(propertyName);
    2.47      }
    2.48  
    2.49      @Override
    2.50 @@ -149,19 +165,7 @@
    2.51  
    2.52      @Override
    2.53      public void runSafe(final Runnable r) {
    2.54 -        class Wrap implements Runnable {
    2.55 -            @Override public void run() {
    2.56 -                Fn.Presenter prev = FnUtils.currentPresenter(browserContext);
    2.57 -                try {
    2.58 -                    r.run();
    2.59 -                } finally {
    2.60 -                    FnUtils.currentPresenter(prev);
    2.61 -                }
    2.62 -                
    2.63 -            }
    2.64 -        }
    2.65 -        Wrap w = new Wrap();
    2.66 -        
    2.67 +        Wrap w = new Wrap(r);
    2.68          if (Platform.isFxApplicationThread()) {
    2.69              w.run();
    2.70          } else {
    2.71 @@ -183,6 +187,24 @@
    2.72      public void close(LoadWS socket) {
    2.73          socket.close();
    2.74      }
    2.75 +
    2.76 +    @Override
    2.77 +    public void run() {
    2.78 +        List<JSObject> objs = new ArrayList<JSObject>();
    2.79 +        List<String> strngs = new ArrayList<String>();
    2.80 +        synchronized (this) {
    2.81 +            for (Map.Entry<JSObject, Set<String>> entry : notifyMutated.entrySet()) {
    2.82 +                for (String propertyName : entry.getValue()) {
    2.83 +                    objs.add(entry.getKey());
    2.84 +                    strngs.add(propertyName);
    2.85 +                }
    2.86 +            }
    2.87 +            notifyMutated.clear();
    2.88 +        }
    2.89 +        Object arrObj = Knockout.toArray(objs.toArray());
    2.90 +        Object arrStr = strngs.toArray();
    2.91 +        Knockout.valuesMutated(arrObj, arrStr);
    2.92 +    }
    2.93      
    2.94      @ServiceProvider(service = Contexts.Provider.class)
    2.95      public static final class Prvdr implements Contexts.Provider {
    2.96 @@ -199,4 +221,24 @@
    2.97              }
    2.98          }
    2.99      }
   2.100 +    
   2.101 +    private class Wrap implements Runnable {
   2.102 +        private final Runnable r;
   2.103 +
   2.104 +        public Wrap(Runnable r) {
   2.105 +            this.r = r;
   2.106 +        }
   2.107 +
   2.108 +        @Override
   2.109 +        public void run() {
   2.110 +            Fn.Presenter prev = FnUtils.currentPresenter(browserContext);
   2.111 +            try {
   2.112 +                r.run();
   2.113 +            } finally {
   2.114 +                FnUtils.currentPresenter(prev);
   2.115 +            }
   2.116 +
   2.117 +        }
   2.118 +    }
   2.119 +    
   2.120  }
     3.1 --- a/ko-fx/src/main/java/org/apidesign/html/kofx/Knockout.java	Thu Sep 12 17:18:18 2013 +0200
     3.2 +++ b/ko-fx/src/main/java/org/apidesign/html/kofx/Knockout.java	Thu Sep 12 17:59:10 2013 +0200
     3.3 @@ -48,15 +48,19 @@
     3.4          return KObject.call("array", arr);
     3.5      }
     3.6      
     3.7 -    @JavaScriptBody(args = { "model", "prop" }, body =
     3.8 -          "if (model) {\n"
     3.9 -        + "  var koProp = model[prop];\n"
    3.10 -        + "  if (koProp && koProp['valueHasMutated']) {\n"
    3.11 -        + "    koProp['valueHasMutated']();\n"
    3.12 +    @JavaScriptBody(args = { "models", "props" }, body =
    3.13 +          "for (var i = 0; i < models.length; i++) { \n"
    3.14 +        + "  var model = models[i];\n"
    3.15 +        + "  var prop = props[i];\n"
    3.16 +        + "  if (model) {\n"
    3.17 +        + "    var koProp = model[prop];\n"
    3.18 +        + "    if (koProp && koProp['valueHasMutated']) {\n"
    3.19 +        + "      koProp['valueHasMutated']();\n"
    3.20 +        + "    }\n"
    3.21          + "  }\n"
    3.22          + "}\n"
    3.23      )
    3.24 -    public native static void valueHasMutated(JSObject model, String prop);
    3.25 +    native static void valuesMutated(Object arrObjs, Object arrProps);
    3.26  
    3.27      @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
    3.28      native static void applyBindings(Object bindings);
     4.1 --- a/ko-fx/src/test/java/org/apidesign/html/kofx/LessCallbacksCheck.java	Thu Sep 12 17:18:18 2013 +0200
     4.2 +++ b/ko-fx/src/test/java/org/apidesign/html/kofx/LessCallbacksCheck.java	Thu Sep 12 17:59:10 2013 +0200
     4.3 @@ -57,4 +57,18 @@
     4.4              assert false : "Don't call for initial value via JsCallbacks:\n" + sw;
     4.5          }
     4.6      }
     4.7 +    
     4.8 +    @KOTest public void dontCallForUpdatedValueBackToJavaVM() {
     4.9 +        LessCalls m = new LessCalls(10).applyBindings();
    4.10 +        assert m.getPlusOne() == 11 : "Expecting 11: " + m.getPlusOne();
    4.11 +        
    4.12 +        m.setValue(11);
    4.13 +        assert m.getPlusOne() == 12 : "Expecting 12: " + m.getPlusOne();
    4.14 +        
    4.15 +        assert sw != null : "StringWriter should be initialized: " + sw;
    4.16 +        
    4.17 +        if (sw.toString().contains("$JsCallbacks$")) {
    4.18 +            assert false : "Don't call for initial value via JsCallbacks:\n" + sw;
    4.19 +        }
    4.20 +    }
    4.21  }