Implementation of knockout binding that does not rely on anything JavaFX specific UniversalKO
authorJaroslav Tulach <jaroslav.tulach@netbeans.org>
Wed, 08 Jan 2014 12:58:45 +0100
branchUniversalKO
changeset 430080b60863878
parent 429 c39b037e0904
child 431 41fbba02a2e1
Implementation of knockout binding that does not rely on anything JavaFX specific
boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java
boot/src/main/java/org/apidesign/html/boot/spi/Fn.java
boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java
boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java
json-tck/src/main/java/net/java/html/js/tests/Bodies.java
json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java
ko-fx/src/main/java/org/netbeans/html/kofx/FXContext.java
ko-fx/src/main/java/org/netbeans/html/kofx/Knockout.java
     1.1 --- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java	Wed Jan 08 12:24:21 2014 +0100
     1.2 +++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java	Wed Jan 08 12:58:45 2014 +0100
     1.3 @@ -46,7 +46,6 @@
     1.4  import java.io.Reader;
     1.5  import java.net.URL;
     1.6  import java.util.ArrayList;
     1.7 -import java.util.Arrays;
     1.8  import java.util.List;
     1.9  import java.util.logging.Level;
    1.10  import java.util.logging.Logger;
    1.11 @@ -60,7 +59,7 @@
    1.12   *
    1.13   * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    1.14   */
    1.15 -public abstract class AbstractFXPresenter implements Fn.Presenter {
    1.16 +public abstract class AbstractFXPresenter implements Fn.Presenter, Fn.ToJavaScript {
    1.17      static final Logger LOG = Logger.getLogger(FXPresenter.class.getName());
    1.18      protected static int cnt;
    1.19      protected List<String> scripts;
    1.20 @@ -218,6 +217,15 @@
    1.21          return arraySize;
    1.22      }
    1.23  
    1.24 +    @Override
    1.25 +    public Object toJavaScript(Object toReturn) {
    1.26 +        if (toReturn instanceof Object[]) {
    1.27 +            return convertArrays((Object[])toReturn);
    1.28 +        } else {
    1.29 +            return toReturn;
    1.30 +        }
    1.31 +    }
    1.32 +
    1.33      private static final class JSFn extends Fn {
    1.34  
    1.35          private final JSObject fn;
     2.1 --- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java	Wed Jan 08 12:24:21 2014 +0100
     2.2 +++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/FXPresenter.java	Wed Jan 08 12:58:45 2014 +0100
     2.3 @@ -95,4 +95,12 @@
     2.4      protected WebView findView(final URL resource) {
     2.5          return FXBrwsr.findWebView(resource, this);
     2.6      }
     2.7 +    
     2.8 +    public void runSafe(Runnable r) {
     2.9 +        if (Platform.isFxApplicationThread()) {
    2.10 +            r.run();
    2.11 +        } else {
    2.12 +            Platform.runLater(r);
    2.13 +        }
    2.14 +    }
    2.15  }
     3.1 --- a/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java	Wed Jan 08 12:24:21 2014 +0100
     3.2 +++ b/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java	Wed Jan 08 12:58:45 2014 +0100
     3.3 @@ -232,4 +232,29 @@
     3.4           */
     3.5          public void loadScript(Reader code) throws Exception;
     3.6      }
     3.7 +    
     3.8 +    /** Additional interface to be implemented by {@link Presenter}s that
     3.9 +     * wish to control what objects are passed into the JavaScript virtual 
    3.10 +     * machine.
    3.11 +     * <p>
    3.12 +     * If a JavaScript engine makes callback to Java method that returns 
    3.13 +     * a value, the {@link #toJavaScript(java.lang.Object)} method is
    3.14 +     * consulted to convert the Java value to something reasonable inside
    3.15 +     * JavaScript VM.
    3.16 +     * <p>
    3.17 +     * <em>Note:</em> The implementation based on <em>JavaFX</em> <code>WebView</code>
    3.18 +     * uses this interface to convert Java arrays to JavaScript ones.
    3.19 +     * 
    3.20 +     * @see Presenter
    3.21 +     * @since 0.7
    3.22 +     */
    3.23 +    public interface ToJavaScript {
    3.24 +        /** Convert a Java return value into some object suitable for
    3.25 +         * JavaScript virtual machine.
    3.26 +         * 
    3.27 +         * @param toReturn the Java object to be returned
    3.28 +         * @return the replacement value to return instead
    3.29 +         */
    3.30 +        public Object toJavaScript(Object toReturn);
    3.31 +    }
    3.32  }
     4.1 --- a/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java	Wed Jan 08 12:24:21 2014 +0100
     4.2 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java	Wed Jan 08 12:58:45 2014 +0100
     4.3 @@ -312,7 +312,7 @@
     4.4                  }
     4.5                  source.append("    ");
     4.6                  if (m.getReturnType().getKind() != TypeKind.VOID) {
     4.7 -                    source.append("return ");
     4.8 +                    source.append("Object $ret = ");
     4.9                  }
    4.10                  if (isStatic) {
    4.11                      source.append(((TypeElement)m.getEnclosingElement()).getQualifiedName());
    4.12 @@ -332,6 +332,11 @@
    4.13                  source.append(");\n");
    4.14                  if (m.getReturnType().getKind() == TypeKind.VOID) {
    4.15                      source.append("    return null;\n");
    4.16 +                } else {
    4.17 +                    source.append("    if (p instanceof org.apidesign.html.boot.spi.Fn.ToJavaScript) {\n");
    4.18 +                    source.append("      $ret = ((org.apidesign.html.boot.spi.Fn.ToJavaScript)p).toJavaScript($ret);\n");
    4.19 +                    source.append("    }\n");
    4.20 +                    source.append("    return $ret;\n");
    4.21                  }
    4.22                  if (processingEnv.getSourceVersion().compareTo(SourceVersion.RELEASE_7) >= 0) {
    4.23                      source.append("    }\n");
     5.1 --- a/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java	Wed Jan 08 12:24:21 2014 +0100
     5.2 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JsClassLoader.java	Wed Jan 08 12:58:45 2014 +0100
     5.3 @@ -85,6 +85,9 @@
     5.4          if (name.equals(Fn.Presenter.class.getName())) {
     5.5              return Fn.Presenter.class;
     5.6          }
     5.7 +        if (name.equals(Fn.ToJavaScript.class.getName())) {
     5.8 +            return Fn.ToJavaScript.class;
     5.9 +        }
    5.10          if (name.equals(FnUtils.class.getName())) {
    5.11              return FnUtils.class;
    5.12          }
     6.1 --- a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java	Wed Jan 08 12:24:21 2014 +0100
     6.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java	Wed Jan 08 12:58:45 2014 +0100
     6.3 @@ -57,7 +57,10 @@
     6.4      static native void callback(Runnable r);
     6.5  
     6.6      @JavaScriptBody(args = {"c"}, javacall = true, body = "return c.@java.util.concurrent.Callable::call()();")
     6.7 -    static native Object callback(Callable<Boolean> c);
     6.8 +    static native Object callback(Callable<? extends Object> c);
     6.9 +
    6.10 +    @JavaScriptBody(args = {"c", "v"}, javacall = true, body = "var arr = c.@java.util.concurrent.Callable::call()(); arr.push(v); return arr;")
    6.11 +    static native Object callbackAndPush(Callable<String[]> c, String v);
    6.12      
    6.13      @JavaScriptBody(args = { "v" }, body = "return v;")
    6.14      public static native Object id(Object v);
     7.1 --- a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java	Wed Jan 08 12:24:21 2014 +0100
     7.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java	Wed Jan 08 12:58:45 2014 +0100
     7.3 @@ -42,6 +42,7 @@
     7.4   */
     7.5  package net.java.html.js.tests;
     7.6  
     7.7 +import java.util.Arrays;
     7.8  import java.util.concurrent.Callable;
     7.9  import org.apidesign.html.json.tck.KOTest;
    7.10  
    7.11 @@ -174,6 +175,24 @@
    7.12          assert "Ahoj".equals(arr[0]) : "From a Java point of view it remains: " + arr[0];
    7.13      }
    7.14  
    7.15 +    @KOTest
    7.16 +    public void callbackWithArray() {
    7.17 +        class A implements Callable<String[]> {
    7.18 +            @Override
    7.19 +            public String[] call() throws Exception {
    7.20 +                return new String[] { "Hello" };
    7.21 +            }
    7.22 +        }
    7.23 +        Callable<String[]> a = new A();
    7.24 +        Object b = Bodies.callbackAndPush(a, "World!");
    7.25 +        assert b instanceof Object[] : "Returns an array: " + b;
    7.26 +        Object[] arr = (Object[]) b;
    7.27 +        String str = Arrays.toString(arr);
    7.28 +        assert arr.length == 2 : "Size is two " + str;
    7.29 +        assert "Hello".equals(arr[0]) : "Hello expected: " + arr[0];
    7.30 +        assert "World!".equals(arr[1]) : "World! expected: " + arr[1];
    7.31 +    }
    7.32 +
    7.33      @KOTest public void truth() {
    7.34          assert Bodies.truth() : "True is true";
    7.35      }
     8.1 --- a/ko-fx/src/main/java/org/netbeans/html/kofx/FXContext.java	Wed Jan 08 12:24:21 2014 +0100
     8.2 +++ b/ko-fx/src/main/java/org/netbeans/html/kofx/FXContext.java	Wed Jan 08 12:58:45 2014 +0100
     8.3 @@ -45,11 +45,10 @@
     8.4  import java.io.Closeable;
     8.5  import java.io.IOException;
     8.6  import java.io.InputStream;
     8.7 +import java.lang.reflect.Method;
     8.8  import java.util.ServiceLoader;
     8.9  import java.util.logging.Logger;
    8.10 -import javafx.application.Platform;
    8.11  import net.java.html.js.JavaScriptBody;
    8.12 -import netscape.javascript.JSObject;
    8.13  import org.apidesign.html.boot.spi.Fn;
    8.14  import org.apidesign.html.context.spi.Contexts;
    8.15  import org.apidesign.html.json.spi.FunctionBinding;
    8.16 @@ -69,7 +68,7 @@
    8.17   * @author Jaroslav Tulach <jtulach@netbeans.org>
    8.18   */
    8.19  public final class FXContext
    8.20 -implements Technology.BatchInit<JSObject>, Transfer, WSTransfer<LoadWS> {
    8.21 +implements Technology.BatchInit<Object>, Transfer, WSTransfer<LoadWS> {
    8.22      static final Logger LOG = Logger.getLogger(FXContext.class.getName());
    8.23      private static Boolean javaScriptEnabled;
    8.24      private final Fn.Presenter browserContext;
    8.25 @@ -96,7 +95,7 @@
    8.26  
    8.27  
    8.28      @Override
    8.29 -    public JSObject wrapModel(Object model, PropertyBinding[] propArr, FunctionBinding[] funcArr) {
    8.30 +    public Object wrapModel(Object model, PropertyBinding[] propArr, FunctionBinding[] funcArr) {
    8.31          String[] propNames = new String[propArr.length];
    8.32          boolean[] propReadOnly = new boolean[propArr.length];
    8.33          Object[] propValues = new Object[propArr.length];
    8.34 @@ -109,41 +108,41 @@
    8.35          for (int i = 0; i < funcNames.length; i++) {
    8.36              funcNames[i] = funcArr[i].getFunctionName();
    8.37          }
    8.38 -        
    8.39 -        return Knockout.wrapModel(model, 
    8.40 -            propNames, propReadOnly, Knockout.toArray(propValues), propArr, 
    8.41 +        Object ret = Knockout.wrapModel(model, 
    8.42 +            propNames, propReadOnly, propValues, propArr,
    8.43              funcNames, funcArr
    8.44          );
    8.45 +        return ret;
    8.46      }
    8.47      
    8.48      @Override
    8.49 -    public JSObject wrapModel(Object model) {
    8.50 +    public Object wrapModel(Object model) {
    8.51          throw new UnsupportedOperationException();
    8.52      }
    8.53  
    8.54      @Override
    8.55 -    public void bind(PropertyBinding b, Object model, JSObject data) {
    8.56 +    public void bind(PropertyBinding b, Object model, Object data) {
    8.57          throw new UnsupportedOperationException();
    8.58      }
    8.59  
    8.60      @Override
    8.61 -    public void valueHasMutated(JSObject data, String propertyName) {
    8.62 +    public void valueHasMutated(Object data, String propertyName) {
    8.63          Knockout.valueHasMutated(data, propertyName);
    8.64      }
    8.65  
    8.66      @Override
    8.67 -    public void expose(FunctionBinding fb, Object model, JSObject d) {
    8.68 +    public void expose(FunctionBinding fb, Object model, Object d) {
    8.69          throw new UnsupportedOperationException();
    8.70      }
    8.71  
    8.72      @Override
    8.73 -    public void applyBindings(JSObject data) {
    8.74 +    public void applyBindings(Object data) {
    8.75          Knockout.applyBindings(data);
    8.76      }
    8.77  
    8.78      @Override
    8.79      public Object wrapArray(Object[] arr) {
    8.80 -        return Knockout.toArray(arr);
    8.81 +        return arr;
    8.82      }
    8.83  
    8.84      @Override
    8.85 @@ -158,10 +157,7 @@
    8.86  
    8.87      @Override
    8.88      public <M> M toModel(Class<M> modelClass, Object data) {
    8.89 -        if (data instanceof JSObject) {
    8.90 -            data = ((JSObject)data).getMember("ko-fx.model"); // NOI18N
    8.91 -        }
    8.92 -        return modelClass.cast(data);
    8.93 +        return modelClass.cast(Knockout.toModel(data));
    8.94      }
    8.95  
    8.96      @Override
    8.97 @@ -173,19 +169,24 @@
    8.98      public void runSafe(final Runnable r) {
    8.99          class Wrap implements Runnable {
   8.100              @Override public void run() {
   8.101 -                try (Closeable c = Fn.activate(browserContext)) {
   8.102 +                Closeable c = Fn.activate(browserContext);
   8.103 +                try {
   8.104                      r.run();
   8.105 -                } catch (IOException ex) {
   8.106 -                    // cannot be thrown
   8.107 +                } finally {
   8.108 +                    try {
   8.109 +                        c.close();
   8.110 +                    } catch (IOException ex) {
   8.111 +                        // cannot be thrown
   8.112 +                    }
   8.113                  }
   8.114              }
   8.115          }
   8.116          Wrap w = new Wrap();
   8.117 -        
   8.118 -        if (Platform.isFxApplicationThread()) {
   8.119 -            w.run();
   8.120 -        } else {
   8.121 -            Platform.runLater(w);
   8.122 +        try {
   8.123 +            Method m = browserContext.getClass().getMethod("runSafe", Runnable.class);
   8.124 +            m.invoke(browserContext, w);
   8.125 +        } catch (Exception ex) {
   8.126 +            throw new IllegalStateException(ex);
   8.127          }
   8.128      }
   8.129  
     9.1 --- a/ko-fx/src/main/java/org/netbeans/html/kofx/Knockout.java	Wed Jan 08 12:24:21 2014 +0100
     9.2 +++ b/ko-fx/src/main/java/org/netbeans/html/kofx/Knockout.java	Wed Jan 08 12:58:45 2014 +0100
     9.3 @@ -45,7 +45,6 @@
     9.4  import net.java.html.js.JavaScriptBody;
     9.5  import net.java.html.js.JavaScriptResource;
     9.6  import net.java.html.json.Model;
     9.7 -import netscape.javascript.JSObject;
     9.8  import org.apidesign.html.json.spi.FunctionBinding;
     9.9  import org.apidesign.html.json.spi.PropertyBinding;
    9.10  
    9.11 @@ -60,16 +59,14 @@
    9.12   */
    9.13  @JavaScriptResource("knockout-2.2.1.js")
    9.14  final class Knockout {
    9.15 -    static final JSObject KObject;
    9.16      static {
    9.17          Console.register();
    9.18 -        KObject = (JSObject) kObj();
    9.19 -    }
    9.20 -
    9.21 -    static Object toArray(Object[] arr) {
    9.22 -        return KObject.call("array", arr);
    9.23 +        loadKnockout();
    9.24      }
    9.25      
    9.26 +    @JavaScriptBody(args = {  }, body = "")
    9.27 +    private static native void loadKnockout();
    9.28 +
    9.29      @JavaScriptBody(args = { "model", "prop" }, body =
    9.30            "if (model) {\n"
    9.31          + "  var koProp = model[prop];\n"
    9.32 @@ -78,21 +75,11 @@
    9.33          + "  }\n"
    9.34          + "}\n"
    9.35      )
    9.36 -    public native static void valueHasMutated(JSObject model, String prop);
    9.37 +    public native static void valueHasMutated(Object model, String prop);
    9.38  
    9.39      @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
    9.40      native static void applyBindings(Object bindings);
    9.41      
    9.42 -    @JavaScriptBody(args = {}, body =
    9.43 -              "  var k = {};"
    9.44 -            + "  k.array= function() {"
    9.45 -            + "    return Array.prototype.slice.call(arguments);"
    9.46 -            + "  };"
    9.47 -            + "  return k;"
    9.48 -    )
    9.49 -    private static native Object kObj();
    9.50 -        
    9.51 -        
    9.52      @JavaScriptBody(
    9.53          javacall = true,
    9.54          args = {"model", "propNames", "propReadOnly", "propValues", "propArr", "funcNames", "funcArr"},
    9.55 @@ -137,9 +124,12 @@
    9.56          + "}\n"
    9.57          + "return ret;\n"
    9.58          )
    9.59 -    static native JSObject wrapModel(
    9.60 +    static native Object wrapModel(
    9.61          Object model,
    9.62          String[] propNames, boolean[] propReadOnly, Object propValues, PropertyBinding[] propArr,
    9.63          String[] funcNames, FunctionBinding[] funcArr
    9.64      );
    9.65 +    
    9.66 +    @JavaScriptBody(args = { "o" }, body = "return o['ko-fx.model'] ? o['ko-fx.model'] : o;")
    9.67 +    static native Object toModel(Object wrapper);
    9.68  }