Implementation of knockout binding that does not rely on anything JavaFX specific
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 }