Cleanup properties of the JavaScript object once its model gets garbage collected
1.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/FXContext.java Mon Dec 08 20:59:41 2014 +0100
1.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/FXContext.java Mon Dec 08 21:04:35 2014 +0100
1.3 @@ -43,7 +43,6 @@
1.4 package org.netbeans.html.ko4j;
1.5
1.6 import java.io.ByteArrayOutputStream;
1.7 -import java.io.Closeable;
1.8 import java.io.IOException;
1.9 import java.io.InputStream;
1.10 import java.io.InputStreamReader;
1.11 @@ -90,7 +89,7 @@
1.12 funcNames[i] = funcArr[i].getFunctionName();
1.13 }
1.14 Object ret = getJSObject();
1.15 - Knockout.wrapModel(new Knockout(model, propArr, funcArr),
1.16 + Knockout.wrapModel(new Knockout(model, ret, propArr, funcArr),
1.17 ret,
1.18 propNames, propReadOnly, propValues,
1.19 funcNames
1.20 @@ -101,11 +100,16 @@
1.21 private Object getJSObject() {
1.22 int len = 64;
1.23 if (jsObjects != null && jsIndex < (len = jsObjects.length)) {
1.24 - return jsObjects[jsIndex++];
1.25 + Object ret = jsObjects[jsIndex];
1.26 + jsObjects[jsIndex] = null;
1.27 + jsIndex++;
1.28 + return ret;
1.29 }
1.30 jsObjects = Knockout.allocJS(len * 2);
1.31 jsIndex = 1;
1.32 - return jsObjects[0];
1.33 + Object ret = jsObjects[0];
1.34 + jsObjects[0] = null;
1.35 + return ret;
1.36 }
1.37
1.38 @Override
1.39 @@ -120,11 +124,13 @@
1.40
1.41 @Override
1.42 public void valueHasMutated(Object data, String propertyName) {
1.43 + Knockout.cleanUp();
1.44 Knockout.valueHasMutated(data, propertyName, null, null);
1.45 }
1.46
1.47 @Override
1.48 public void valueHasMutated(Object data, String propertyName, Object oldValue, Object newValue) {
1.49 + Knockout.cleanUp();
1.50 Knockout.valueHasMutated(data, propertyName, oldValue, newValue);
1.51 }
1.52
2.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Mon Dec 08 20:59:41 2014 +0100
2.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Mon Dec 08 21:04:35 2014 +0100
2.3 @@ -42,6 +42,7 @@
2.4 */
2.5 package org.netbeans.html.ko4j;
2.6
2.7 +import java.lang.ref.ReferenceQueue;
2.8 import java.lang.ref.WeakReference;
2.9 import net.java.html.js.JavaScriptBody;
2.10 import net.java.html.js.JavaScriptResource;
2.11 @@ -60,12 +61,16 @@
2.12 */
2.13 @JavaScriptResource("knockout-3.2.0.debug.js")
2.14 final class Knockout extends WeakReference<Object> {
2.15 - private final PropertyBinding[] props;
2.16 - private final FunctionBinding[] funcs;
2.17 + private static final ReferenceQueue<Object> QUEUE = new ReferenceQueue();
2.18 +
2.19 + private PropertyBinding[] props;
2.20 + private FunctionBinding[] funcs;
2.21 + private Object js;
2.22 private Object strong;
2.23
2.24 - public Knockout(Object model, PropertyBinding[] props, FunctionBinding[] funcs) {
2.25 - super(model);
2.26 + public Knockout(Object model, Object js, PropertyBinding[] props, FunctionBinding[] funcs) {
2.27 + super(model, QUEUE);
2.28 + this.js = js;
2.29 this.props = new PropertyBinding[props.length];
2.30 for (int i = 0; i < props.length; i++) {
2.31 this.props[i] = props[i].weak();
2.32 @@ -76,6 +81,19 @@
2.33 }
2.34 }
2.35
2.36 + static void cleanUp() {
2.37 + for (;;) {
2.38 + Knockout ko = (Knockout)QUEUE.poll();
2.39 + if (ko == null) {
2.40 + return;
2.41 + }
2.42 + clean(ko.js);
2.43 + ko.js = null;
2.44 + ko.props = null;
2.45 + ko.funcs = null;
2.46 + }
2.47 + }
2.48 +
2.49 final void hold() {
2.50 strong = get();
2.51 }
2.52 @@ -130,14 +148,15 @@
2.53 @JavaScriptBody(
2.54 javacall = true,
2.55 wait4js = false,
2.56 - args = { "self", "ret", "propNames", "propReadOnly", "propValues", "funcNames" },
2.57 + args = { "thiz", "ret", "propNames", "propReadOnly", "propValues", "funcNames" },
2.58 body =
2.59 - "Object.defineProperty(ret, 'ko4j', { value : self });\n"
2.60 + "Object.defineProperty(ret, 'ko4j', { value : thiz });\n"
2.61 + "function koComputed(index, name, readOnly, value) {\n"
2.62 + " var trigger = ko['observable']()['extend']({'notify':'always'});"
2.63 + " function realGetter() {\n"
2.64 + + " var self = ret['ko4j'];\n"
2.65 + " try {\n"
2.66 - + " var v = self.@org.netbeans.html.ko4j.Knockout::getValue(I)(index);\n"
2.67 + + " var v = self ? self.@org.netbeans.html.ko4j.Knockout::getValue(I)(index) : null;\n"
2.68 + " return v;\n"
2.69 + " } catch (e) {\n"
2.70 + " alert(\"Cannot call getValue on \" + self + \" prop: \" + name + \" error: \" + e);\n"
2.71 @@ -156,6 +175,8 @@
2.72 + " };\n"
2.73 + " if (!readOnly) {\n"
2.74 + " bnd['write'] = function(val) {\n"
2.75 + + " var self = ret['ko4j'];\n"
2.76 + + " if (!self) return;\n"
2.77 + " var model = val['ko4j'];\n"
2.78 + " self.@org.netbeans.html.ko4j.Knockout::setValue(ILjava/lang/Object;)(index, model ? model : val);\n"
2.79 + " };\n"
2.80 @@ -172,6 +193,8 @@
2.81 + "}\n"
2.82 + "function koExpose(index, name) {\n"
2.83 + " ret[name] = function(data, ev) {\n"
2.84 + + " var self = ret['ko4j'];\n"
2.85 + + " if (!self) return;\n"
2.86 + " self.@org.netbeans.html.ko4j.Knockout::call(ILjava/lang/Object;Ljava/lang/Object;)(index, data, ev);\n"
2.87 + " };\n"
2.88 + "}\n"
2.89 @@ -186,6 +209,15 @@
2.90 String[] funcNames
2.91 );
2.92
2.93 + @JavaScriptBody(args = { "js" }, wait4js = false, body =
2.94 + "delete js['ko4j'];\n" +
2.95 + "for (var p in js) {\n" +
2.96 + " delete js[p];\n" +
2.97 + "};\n" +
2.98 + "\n"
2.99 + )
2.100 + private static native void clean(Object js);
2.101 +
2.102 @JavaScriptBody(args = { "o" }, body = "return o['ko4j'] ? o['ko4j'] : o;")
2.103 private static native Object toModelImpl(Object wrapper);
2.104 static Object toModel(Object wrapper) {