ko/fx/src/main/java/org/apidesign/bck2brwsr/kofx/Knockout.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Wed, 26 Jun 2013 19:57:38 +0200
branchclassloader
changeset 1230 466c30fd9cb0
permissions -rw-r--r--
Adding in launcher based support for knockout
     1 /**
     2  * Back 2 Browser Bytecode Translator
     3  * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4  *
     5  * This program is free software: you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation, version 2 of the License.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program. Look for COPYING file in the top folder.
    16  * If not, see http://opensource.org/licenses/GPL-2.0.
    17  */
    18 package org.apidesign.bck2brwsr.kofx;
    19 
    20 import java.io.BufferedReader;
    21 import java.io.IOException;
    22 import java.io.InputStream;
    23 import java.io.InputStreamReader;
    24 import java.util.logging.Level;
    25 import java.util.logging.Logger;
    26 import net.java.html.js.JavaScriptBody;
    27 import net.java.html.json.Model;
    28 import netscape.javascript.JSObject;
    29 import org.apidesign.html.json.spi.FunctionBinding;
    30 import org.apidesign.html.json.spi.PropertyBinding;
    31 
    32 /** This is an implementation package - just
    33  * include its JAR on classpath and use official {@link Context} API
    34  * to access the functionality.
    35  * <p>
    36  * Provides binding between {@link Model models} and knockout.js running
    37  * inside a JavaFX WebView. 
    38  *
    39  * @author Jaroslav Tulach <jtulach@netbeans.org>
    40  */
    41 public final class Knockout {
    42     private static final Logger LOG = Logger.getLogger(Knockout.class.getName());
    43     /** used by tests */
    44     static Knockout next;
    45     private final Object model;
    46 
    47     Knockout(Object model) {
    48         this.model = model == null ? this : model;
    49     }
    50     
    51     public Object koData() {
    52         return model;
    53     }
    54 
    55     static Object toArray(Object[] arr) {
    56         return InvokeJS.KObject.call("array", arr);
    57     }
    58     
    59     private static int cnt;
    60     public static <M> Knockout createBinding(Object model) {
    61         Object bindings = InvokeJS.create(model, ++cnt);
    62         return new Knockout(bindings);
    63     }
    64 
    65     public void valueHasMutated(String prop) {
    66         valueHasMutated((JSObject) model, prop);
    67     }
    68     public static void valueHasMutated(JSObject model, String prop) {
    69         LOG.log(Level.FINE, "property mutated: {0}", prop);
    70         try {
    71             Object koProp = model.getMember(prop);
    72             if (koProp instanceof JSObject) {
    73                 ((JSObject)koProp).call("valueHasMutated");
    74             }
    75         } catch (Throwable t) {
    76             LOG.log(Level.WARNING, "valueHasMutated failed for " + model + " prop: " + prop, t);
    77         }
    78     }
    79 
    80     static void bind(
    81         Object bindings, Object model, PropertyBinding pb, boolean primitive, boolean array
    82     ) {
    83         final String prop = pb.getPropertyName();
    84         try {
    85             InvokeJS.bind(bindings, pb, prop, "getValue", pb.isReadOnly() ? null : "setValue", primitive, array);
    86             
    87             ((JSObject)bindings).setMember("ko-fx.model", model);
    88             LOG.log(Level.FINE, "binding defined for {0}: {1}", new Object[]{prop, ((JSObject)bindings).getMember(prop)});
    89         } catch (Throwable ex) {
    90             LOG.log(Level.WARNING, "binding failed for {0} on {1}", new Object[]{prop, bindings});
    91         }
    92     }
    93     static void expose(Object bindings, FunctionBinding f) {
    94         final String prop = f.getFunctionName();
    95         try {
    96             InvokeJS.expose(bindings, f, prop, "call");
    97         } catch (Throwable ex) {
    98             LOG.log(Level.SEVERE, "Cannot define binding for " + prop + " in model " + f, ex);
    99         }
   100     }
   101     
   102     static void applyBindings(Object bindings) {
   103         InvokeJS.applyBindings(bindings);
   104     }
   105     
   106     private static final class InvokeJS {
   107         static final JSObject KObject;
   108 
   109         static {
   110             final InputStream koScript = Knockout.class.getResourceAsStream("knockout-2.2.1.js");
   111             assert koScript != null : "Can't load knockout.js";
   112             BufferedReader r = new BufferedReader(new InputStreamReader(koScript));
   113             StringBuilder sb = new StringBuilder();
   114             for (;;) {
   115                 try {
   116                     String l = r.readLine();
   117                     if (l == null) {
   118                         break;
   119                     }
   120                     sb.append(l).append('\n');
   121                 } catch (IOException ex) {
   122                     throw new IllegalStateException(ex);
   123                 }
   124             }
   125             exec(sb.toString());
   126             Object ko = exec("ko");
   127             assert ko != null : "Knockout library successfully defined 'ko'";
   128 
   129             Console.register();
   130             KObject = (JSObject) kObj();
   131         }
   132         
   133         @JavaScriptBody(args = { "s" }, body = "return eval(s);")
   134         private static native Object exec(String s);
   135         
   136         @JavaScriptBody(args = {}, body =
   137                   "  var k = {};"
   138                 + "  k.array= function() {"
   139                 + "    return Array.prototype.slice.call(arguments);"
   140                 + "  };"
   141                 + "  return k;"
   142         )
   143         private static native Object kObj();
   144         
   145         @JavaScriptBody(args = { "value", "cnt " }, body =
   146                   "    var ret = {};"
   147                 + "    ret.toString = function() { return 'KObject' + cnt + ' value: ' + value + ' props: ' + Object.keys(this); };"
   148                 + "    return ret;"
   149         )
   150         static native Object create(Object value, int cnt);
   151         
   152         @JavaScriptBody(args = { "bindings", "model", "prop", "sig" }, body = 
   153                 "    bindings[prop] = function(data, ev) {"
   154               //            + "         console.log(\"  callback on prop: \" + prop);"
   155               + "      model[sig](data, ev);"
   156               + "    };"
   157         )
   158         static native Object expose(Object bindings, Object model, String prop, String sig);
   159 
   160         
   161         @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive", "array" }, body = 
   162                   "    var bnd = {"
   163                 + "      read: function() {"
   164                 + "      try {"
   165                 + "        var v = model[getter]();"
   166         //        + "      console.log(\" getter value \" + v + \" for property \" + prop);"
   167         //        + "      try { v = v.koData(); } catch (ignore) {"
   168         //        + "        console.log(\"Cannot convert to koData: \" + ignore);"
   169         //        + "      };"
   170         //        + "      console.log(\" getter ret value \" + v);"
   171         //        + "      for (var pn in v) {"
   172         //        + "         console.log(\"  prop: \" + pn + \" + in + \" + v + \" = \" + v[pn]);"
   173         //        + "         if (typeof v[pn] == \"function\") console.log(\"  its function value:\" + v[pn]());"
   174         //        + "      }"
   175         //        + "      console.log(\" all props printed for \" + (typeof v));"
   176                 + "        return v;"
   177                 + "      } catch (e) {"
   178                 + "        alert(\"Cannot call \" + getter + \" on \" + model + \" error: \" + e);"
   179                 + "      }"
   180                 + "    },"
   181                 + "    owner: bindings"
   182         //        + "  ,deferEvaluation: true"
   183                 + "    };"
   184                 + "    if (setter != null) {"
   185                 + "      bnd.write = function(val) {"
   186                 + "        model[setter](primitive ? new Number(val) : val);"
   187                 + "      };"
   188                 + "    };"
   189                 + "    bindings[prop] = ko.computed(bnd);"
   190         )
   191         static native void bind(Object binding, Object model, String prop, String getter, String setter, boolean primitive, boolean array);
   192 
   193         @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
   194         private static native void applyBindings(Object bindings);
   195     }
   196 }