ko/fx/src/main/java/org/apidesign/bck2brwsr/kofx/Knockout.java
branchclassloader
changeset 1232 17ca7fe5c486
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/ko/fx/src/main/java/org/apidesign/bck2brwsr/kofx/Knockout.java	Wed Jun 26 20:11:13 2013 +0200
     1.3 @@ -0,0 +1,196 @@
     1.4 +/**
     1.5 + * Back 2 Browser Bytecode Translator
     1.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     1.7 + *
     1.8 + * This program is free software: you can redistribute it and/or modify
     1.9 + * it under the terms of the GNU General Public License as published by
    1.10 + * the Free Software Foundation, version 2 of the License.
    1.11 + *
    1.12 + * This program is distributed in the hope that it will be useful,
    1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.15 + * GNU General Public License for more details.
    1.16 + *
    1.17 + * You should have received a copy of the GNU General Public License
    1.18 + * along with this program. Look for COPYING file in the top folder.
    1.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    1.20 + */
    1.21 +package org.apidesign.bck2brwsr.kofx;
    1.22 +
    1.23 +import java.io.BufferedReader;
    1.24 +import java.io.IOException;
    1.25 +import java.io.InputStream;
    1.26 +import java.io.InputStreamReader;
    1.27 +import java.util.logging.Level;
    1.28 +import java.util.logging.Logger;
    1.29 +import net.java.html.js.JavaScriptBody;
    1.30 +import net.java.html.json.Model;
    1.31 +import netscape.javascript.JSObject;
    1.32 +import org.apidesign.html.json.spi.FunctionBinding;
    1.33 +import org.apidesign.html.json.spi.PropertyBinding;
    1.34 +
    1.35 +/** This is an implementation package - just
    1.36 + * include its JAR on classpath and use official {@link Context} API
    1.37 + * to access the functionality.
    1.38 + * <p>
    1.39 + * Provides binding between {@link Model models} and knockout.js running
    1.40 + * inside a JavaFX WebView. 
    1.41 + *
    1.42 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    1.43 + */
    1.44 +public final class Knockout {
    1.45 +    private static final Logger LOG = Logger.getLogger(Knockout.class.getName());
    1.46 +    /** used by tests */
    1.47 +    static Knockout next;
    1.48 +    private final Object model;
    1.49 +
    1.50 +    Knockout(Object model) {
    1.51 +        this.model = model == null ? this : model;
    1.52 +    }
    1.53 +    
    1.54 +    public Object koData() {
    1.55 +        return model;
    1.56 +    }
    1.57 +
    1.58 +    static Object toArray(Object[] arr) {
    1.59 +        return InvokeJS.KObject.call("array", arr);
    1.60 +    }
    1.61 +    
    1.62 +    private static int cnt;
    1.63 +    public static <M> Knockout createBinding(Object model) {
    1.64 +        Object bindings = InvokeJS.create(model, ++cnt);
    1.65 +        return new Knockout(bindings);
    1.66 +    }
    1.67 +
    1.68 +    public void valueHasMutated(String prop) {
    1.69 +        valueHasMutated((JSObject) model, prop);
    1.70 +    }
    1.71 +    public static void valueHasMutated(JSObject model, String prop) {
    1.72 +        LOG.log(Level.FINE, "property mutated: {0}", prop);
    1.73 +        try {
    1.74 +            Object koProp = model.getMember(prop);
    1.75 +            if (koProp instanceof JSObject) {
    1.76 +                ((JSObject)koProp).call("valueHasMutated");
    1.77 +            }
    1.78 +        } catch (Throwable t) {
    1.79 +            LOG.log(Level.WARNING, "valueHasMutated failed for " + model + " prop: " + prop, t);
    1.80 +        }
    1.81 +    }
    1.82 +
    1.83 +    static void bind(
    1.84 +        Object bindings, Object model, PropertyBinding pb, boolean primitive, boolean array
    1.85 +    ) {
    1.86 +        final String prop = pb.getPropertyName();
    1.87 +        try {
    1.88 +            InvokeJS.bind(bindings, pb, prop, "getValue", pb.isReadOnly() ? null : "setValue", primitive, array);
    1.89 +            
    1.90 +            ((JSObject)bindings).setMember("ko-fx.model", model);
    1.91 +            LOG.log(Level.FINE, "binding defined for {0}: {1}", new Object[]{prop, ((JSObject)bindings).getMember(prop)});
    1.92 +        } catch (Throwable ex) {
    1.93 +            LOG.log(Level.WARNING, "binding failed for {0} on {1}", new Object[]{prop, bindings});
    1.94 +        }
    1.95 +    }
    1.96 +    static void expose(Object bindings, FunctionBinding f) {
    1.97 +        final String prop = f.getFunctionName();
    1.98 +        try {
    1.99 +            InvokeJS.expose(bindings, f, prop, "call");
   1.100 +        } catch (Throwable ex) {
   1.101 +            LOG.log(Level.SEVERE, "Cannot define binding for " + prop + " in model " + f, ex);
   1.102 +        }
   1.103 +    }
   1.104 +    
   1.105 +    static void applyBindings(Object bindings) {
   1.106 +        InvokeJS.applyBindings(bindings);
   1.107 +    }
   1.108 +    
   1.109 +    private static final class InvokeJS {
   1.110 +        static final JSObject KObject;
   1.111 +
   1.112 +        static {
   1.113 +            final InputStream koScript = Knockout.class.getResourceAsStream("knockout-2.2.1.js");
   1.114 +            assert koScript != null : "Can't load knockout.js";
   1.115 +            BufferedReader r = new BufferedReader(new InputStreamReader(koScript));
   1.116 +            StringBuilder sb = new StringBuilder();
   1.117 +            for (;;) {
   1.118 +                try {
   1.119 +                    String l = r.readLine();
   1.120 +                    if (l == null) {
   1.121 +                        break;
   1.122 +                    }
   1.123 +                    sb.append(l).append('\n');
   1.124 +                } catch (IOException ex) {
   1.125 +                    throw new IllegalStateException(ex);
   1.126 +                }
   1.127 +            }
   1.128 +            exec(sb.toString());
   1.129 +            Object ko = exec("ko");
   1.130 +            assert ko != null : "Knockout library successfully defined 'ko'";
   1.131 +
   1.132 +            Console.register();
   1.133 +            KObject = (JSObject) kObj();
   1.134 +        }
   1.135 +        
   1.136 +        @JavaScriptBody(args = { "s" }, body = "return eval(s);")
   1.137 +        private static native Object exec(String s);
   1.138 +        
   1.139 +        @JavaScriptBody(args = {}, body =
   1.140 +                  "  var k = {};"
   1.141 +                + "  k.array= function() {"
   1.142 +                + "    return Array.prototype.slice.call(arguments);"
   1.143 +                + "  };"
   1.144 +                + "  return k;"
   1.145 +        )
   1.146 +        private static native Object kObj();
   1.147 +        
   1.148 +        @JavaScriptBody(args = { "value", "cnt " }, body =
   1.149 +                  "    var ret = {};"
   1.150 +                + "    ret.toString = function() { return 'KObject' + cnt + ' value: ' + value + ' props: ' + Object.keys(this); };"
   1.151 +                + "    return ret;"
   1.152 +        )
   1.153 +        static native Object create(Object value, int cnt);
   1.154 +        
   1.155 +        @JavaScriptBody(args = { "bindings", "model", "prop", "sig" }, body = 
   1.156 +                "    bindings[prop] = function(data, ev) {"
   1.157 +              //            + "         console.log(\"  callback on prop: \" + prop);"
   1.158 +              + "      model[sig](data, ev);"
   1.159 +              + "    };"
   1.160 +        )
   1.161 +        static native Object expose(Object bindings, Object model, String prop, String sig);
   1.162 +
   1.163 +        
   1.164 +        @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive", "array" }, body = 
   1.165 +                  "    var bnd = {"
   1.166 +                + "      read: function() {"
   1.167 +                + "      try {"
   1.168 +                + "        var v = model[getter]();"
   1.169 +        //        + "      console.log(\" getter value \" + v + \" for property \" + prop);"
   1.170 +        //        + "      try { v = v.koData(); } catch (ignore) {"
   1.171 +        //        + "        console.log(\"Cannot convert to koData: \" + ignore);"
   1.172 +        //        + "      };"
   1.173 +        //        + "      console.log(\" getter ret value \" + v);"
   1.174 +        //        + "      for (var pn in v) {"
   1.175 +        //        + "         console.log(\"  prop: \" + pn + \" + in + \" + v + \" = \" + v[pn]);"
   1.176 +        //        + "         if (typeof v[pn] == \"function\") console.log(\"  its function value:\" + v[pn]());"
   1.177 +        //        + "      }"
   1.178 +        //        + "      console.log(\" all props printed for \" + (typeof v));"
   1.179 +                + "        return v;"
   1.180 +                + "      } catch (e) {"
   1.181 +                + "        alert(\"Cannot call \" + getter + \" on \" + model + \" error: \" + e);"
   1.182 +                + "      }"
   1.183 +                + "    },"
   1.184 +                + "    owner: bindings"
   1.185 +        //        + "  ,deferEvaluation: true"
   1.186 +                + "    };"
   1.187 +                + "    if (setter != null) {"
   1.188 +                + "      bnd.write = function(val) {"
   1.189 +                + "        model[setter](primitive ? new Number(val) : val);"
   1.190 +                + "      };"
   1.191 +                + "    };"
   1.192 +                + "    bindings[prop] = ko.computed(bnd);"
   1.193 +        )
   1.194 +        static native void bind(Object binding, Object model, String prop, String getter, String setter, boolean primitive, boolean array);
   1.195 +
   1.196 +        @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
   1.197 +        private static native void applyBindings(Object bindings);
   1.198 +    }
   1.199 +}