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