Knockout bindings for primitive types supported in WebView fx
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 18 Mar 2013 17:17:00 +0100
branchfx
changeset 851c285a78302af
parent 845 859804c78010
child 852 671fc517fe17
Knockout bindings for primitive types supported in WebView
javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java
javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java
     1.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java	Thu Mar 14 09:22:28 2013 +0100
     1.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java	Mon Mar 18 17:17:00 2013 +0100
     1.3 @@ -17,7 +17,14 @@
     1.4   */
     1.5  package org.apidesign.bck2brwsr.htmlpage;
     1.6  
     1.7 +import java.io.BufferedReader;
     1.8 +import java.io.IOException;
     1.9 +import java.io.InputStreamReader;
    1.10  import java.lang.reflect.Method;
    1.11 +import java.util.logging.Level;
    1.12 +import java.util.logging.Logger;
    1.13 +import javafx.scene.web.WebEngine;
    1.14 +import netscape.javascript.JSObject;
    1.15  import org.apidesign.bck2brwsr.core.ExtraJavaScript;
    1.16  import org.apidesign.bck2brwsr.core.JavaScriptBody;
    1.17  
    1.18 @@ -27,19 +34,43 @@
    1.19   */
    1.20  @ExtraJavaScript(resource = "/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js")
    1.21  public class Knockout {
    1.22 +    private static final Logger LOG = Logger.getLogger(Knockout.class.getName());
    1.23      /** used by tests */
    1.24      static Knockout next;
    1.25      
    1.26 -    Knockout() {
    1.27 +    static {
    1.28 +        BufferedReader r = new BufferedReader(new InputStreamReader(Knockout.class.getResourceAsStream("knockout-2.2.1.js")));
    1.29 +        StringBuilder sb = new StringBuilder();
    1.30 +        for (;;) {
    1.31 +            try {
    1.32 +                String l = r.readLine();
    1.33 +                if (l == null) {
    1.34 +                    break;
    1.35 +                }
    1.36 +                sb.append(l).append('\n');
    1.37 +            } catch (IOException ex) {
    1.38 +                throw new IllegalStateException(ex);
    1.39 +            }
    1.40 +        }
    1.41 +        web().executeScript(sb.toString());
    1.42 +        Object ko = web().executeScript("ko");
    1.43 +        assert ko != null : "Knockout library successfully defined 'ko'";
    1.44 +    }
    1.45 +    
    1.46 +    
    1.47 +    private final Object bindings;
    1.48 +    
    1.49 +    Knockout(Object bindings) {
    1.50 +        this.bindings = bindings;
    1.51      }
    1.52      
    1.53      public static <M> Knockout applyBindings(
    1.54          Class<M> modelClass, M model, String[] propsGettersAndSetters
    1.55      ) {
    1.56 -        Knockout bindings = next;
    1.57 +        Object bindings = next;
    1.58          next = null;
    1.59          if (bindings == null) {
    1.60 -            bindings = new Knockout();
    1.61 +            bindings = web().executeScript("new Object()");
    1.62          }
    1.63          for (int i = 0; i < propsGettersAndSetters.length; i += 4) {
    1.64              try {
    1.65 @@ -54,13 +85,16 @@
    1.66              }
    1.67          }
    1.68          applyBindings(bindings);
    1.69 -        return bindings;
    1.70 +        return new Knockout(bindings);
    1.71      }
    1.72  
    1.73      @JavaScriptBody(args = { "prop" }, body =
    1.74          "this[prop].valueHasMutated();"
    1.75      )
    1.76      public void valueHasMutated(String prop) {
    1.77 +        LOG.log(Level.INFO, "property mutated: {0}", prop);
    1.78 +        JSObject koProp = (JSObject) ((JSObject)bindings).getMember(prop);
    1.79 +        koProp.call("valueHasMutated");
    1.80      }
    1.81      
    1.82  
    1.83 @@ -86,8 +120,48 @@
    1.84      private static void bind(
    1.85          Object bindings, Object model, String prop, String getter, String setter, boolean primitive
    1.86      ) {
    1.87 +        WebEngine e = web();
    1.88 +        JSObject bnd = (JSObject) e.executeScript("var x = {}; x.bnd = "
    1.89 +        + "new Function('ko', 'bindings', 'model', 'prop', 'getter', 'setter', 'primitive', '"
    1.90 +        + "var bnd = {"
    1.91 +        + "  read: function() {"
    1.92 +        + "    try {"
    1.93 +        + "      return model[getter]();"
    1.94 +        + "    } catch (e) {"
    1.95 +        + "      alert(\"Cannot call \" + getter + \" on \" + model + \" error: \" + e);"
    1.96 +        + "    }"
    1.97 +        + "  },"
    1.98 +        + "  owner: bindings"
    1.99 +        + "};"
   1.100 +        + "if (setter != null) {"
   1.101 +        + "  bnd.write = function(val) {"
   1.102 +        + "    model[setter](primitive ? new Number(val) : val);"
   1.103 +        + "  };"
   1.104 +        + "};"
   1.105 +        + "bindings[prop] = ko.computed(bnd);'"
   1.106 +        + "); x;");
   1.107 +        
   1.108 +        Object ko = e.executeScript("ko");
   1.109 +        bnd.call("bnd", ko, bindings, model, prop, strip(getter), strip(setter), primitive);
   1.110 +        LOG.log(Level.INFO, "binding defined for {0}: {1}", new Object[]{prop, ((JSObject)bindings).getMember(prop)});
   1.111 +    }
   1.112 +    
   1.113 +    private static String strip(String mangled) {
   1.114 +        if (mangled == null) {
   1.115 +            return null;
   1.116 +        }
   1.117 +        int under = mangled.indexOf("__");
   1.118 +        return mangled.substring(0, under);
   1.119      }
   1.120      
   1.121      @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
   1.122 -    private static void applyBindings(Object bindings) {}
   1.123 +    private static void applyBindings(Object bindings) {
   1.124 +        JSObject ko = (JSObject) web().executeScript("ko");
   1.125 +        ko.call("applyBindings", bindings);
   1.126 +    }
   1.127 +    
   1.128 +    private static WebEngine web() {
   1.129 +        return (WebEngine) System.getProperties().get("webEngine");
   1.130 +    }
   1.131 +    
   1.132  }
     2.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Thu Mar 14 09:22:28 2013 +0100
     2.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Mon Mar 18 17:17:00 2013 +0100
     2.3 @@ -98,7 +98,7 @@
     2.4                  try {
     2.5                      w.append("package " + pkg + ";\n");
     2.6                      w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
     2.7 -                    w.append("final class ").append(className).append(" {\n");
     2.8 +                    w.append("public final class ").append(className).append(" {\n");
     2.9                      w.append("  private boolean locked;\n");
    2.10                      if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
    2.11                          return false;
     3.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Thu Mar 14 09:22:28 2013 +0100
     3.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Mon Mar 18 17:17:00 2013 +0100
     3.3 @@ -110,6 +110,10 @@
     3.4      
     3.5      static class MockKnockout extends Knockout {
     3.6          List<String> mutated = new ArrayList<String>();
     3.7 +
     3.8 +        public MockKnockout() {
     3.9 +            super(new Object());
    3.10 +        }
    3.11          
    3.12          @Override
    3.13          public void valueHasMutated(String prop) {