# HG changeset patch # User Jaroslav Tulach # Date 1364208636 -3600 # Node ID af170d42b5b3dd54c1d7cfb0a716bf02fae3305d # Parent ecbd252fd3a78dfa522d16b8a7ed75a9134f8c8b Support for callbacks from knockout to Java diff -r ecbd252fd3a7 -r af170d42b5b3 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java Fri Mar 22 17:03:32 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java Mon Mar 25 11:50:36 2013 +0100 @@ -43,7 +43,8 @@ } @JavaScriptBody(args = { "object", "property" }, - body = "var p = object[property]; return p ? p : null;" + body = "if (property === null) return object;\n" + + "var p = object[property]; return p ? p : null;" ) private static Object getProperty(Object object, String property) { return null; diff -r ecbd252fd3a7 -r af170d42b5b3 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Fri Mar 22 17:03:32 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Mon Mar 25 11:50:36 2013 +0100 @@ -29,12 +29,13 @@ public class Knockout { /** used by tests */ static Knockout next; - + Knockout() { } public static Knockout applyBindings( - Class modelClass, M model, String[] propsGettersAndSetters + Class modelClass, M model, String[] propsGettersAndSetters, + String[] methodsAndSignatures ) { Knockout bindings = next; next = null; @@ -53,6 +54,11 @@ throw new IllegalStateException(ex.getMessage()); } } + for (int i = 0; i < methodsAndSignatures.length; i += 2) { + expose( + bindings, model, methodsAndSignatures[i], methodsAndSignatures[i + 1] + ); + } applyBindings(bindings); return bindings; } @@ -87,6 +93,14 @@ Object bindings, Object model, String prop, String getter, String setter, boolean primitive ) { } + + @JavaScriptBody(args = { "bindings", "model", "prop", "sig" }, body = + "bindings[prop] = function(data, ev) { model[sig](data, ev); };" + ) + private static void expose( + Object bindings, Object model, String prop, String sig + ) { + } @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);") private static void applyBindings(Object bindings) {} diff -r ecbd252fd3a7 -r af170d42b5b3 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Fri Mar 22 17:03:32 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Mon Mar 25 11:50:36 2013 +0100 @@ -55,6 +55,7 @@ import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; import org.apidesign.bck2brwsr.htmlpage.api.Model; import org.apidesign.bck2brwsr.htmlpage.api.On; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; import org.openide.util.lookup.ServiceProvider; @@ -68,6 +69,7 @@ @SupportedAnnotationTypes({ "org.apidesign.bck2brwsr.htmlpage.api.Model", "org.apidesign.bck2brwsr.htmlpage.api.Page", + "org.apidesign.bck2brwsr.htmlpage.api.OnFunction", "org.apidesign.bck2brwsr.htmlpage.api.On" }) public final class PageProcessor extends AbstractProcessor { @@ -164,6 +166,7 @@ try { StringWriter body = new StringWriter(); List propsGetSet = new ArrayList<>(); + List functions = new ArrayList<>(); Map> propsDeps = new HashMap<>(); if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) { ok = false; @@ -171,6 +174,9 @@ if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps)) { ok = false; } + if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) { + ok = false; + } FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e); w = new OutputStreamWriter(java.openOutputStream()); @@ -208,6 +214,13 @@ } sep = ",\n"; } + w.write("\n }, new String[] {\n"); + sep = ""; + for (String n : functions) { + w.write(sep); + w.write(n); + sep = ",\n"; + } w.write("\n });\n return this;\n}\n"); w.write("public void triggerEvent(Element e, OnEvent ev) {\n"); @@ -275,46 +288,7 @@ continue; } ExecutableElement ee = (ExecutableElement)method; - StringBuilder params = new StringBuilder(); - { - boolean first = true; - for (VariableElement ve : ee.getParameters()) { - if (!first) { - params.append(", "); - } - first = false; - if (ve.asType() == stringType) { - if (ve.getSimpleName().contentEquals("id")) { - params.append('"').append(id).append('"'); - continue; - } - params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(ev, \""); - params.append(ve.getSimpleName().toString()); - params.append("\")"); - continue; - } - if (processingEnv.getTypeUtils().getPrimitiveType(TypeKind.DOUBLE) == ve.asType()) { - params.append("org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(ev, \""); - params.append(ve.getSimpleName().toString()); - params.append("\")"); - continue; - } - String rn = ve.asType().toString(); - int last = rn.lastIndexOf('.'); - if (last >= 0) { - rn = rn.substring(last + 1); - } - if (rn.equals(className)) { - params.append(className).append(".this"); - continue; - } - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, - "@On method can only accept String named 'id' or " + className + " arguments", - ee - ); - return false; - } - } + CharSequence params = wrapParams(ee, id, className, "ev", null); if (!ee.getModifiers().contains(Modifier.STATIC)) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee); ok = false; @@ -659,4 +633,105 @@ e = e.getEnclosingElement(); } } + + private boolean generateFunctions( + Element clazz, StringWriter body, String className, + List enclosedElements, List functions + ) { + for (Element m : enclosedElements) { + if (m.getKind() != ElementKind.METHOD) { + continue; + } + ExecutableElement e = (ExecutableElement)m; + OnFunction onF = e.getAnnotation(OnFunction.class); + if (onF == null) { + continue; + } + if (!e.getModifiers().contains(Modifier.STATIC)) { + processingEnv.getMessager().printMessage( + Diagnostic.Kind.ERROR, "@OnFunction method needs to be static", e + ); + return false; + } + if (e.getModifiers().contains(Modifier.PRIVATE)) { + processingEnv.getMessager().printMessage( + Diagnostic.Kind.ERROR, "@OnFunction method cannot be private", e + ); + return false; + } + if (e.getReturnType().getKind() != TypeKind.VOID) { + processingEnv.getMessager().printMessage( + Diagnostic.Kind.ERROR, "@OnFunction method should return void", e + ); + return false; + } + String n = e.getSimpleName().toString(); + body.append("private void ").append(n).append("(Object data, Object ev) {\n"); + body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("("); + body.append(wrapParams(e, null, className, "ev", "data")); + body.append(");\n"); + body.append("}\n"); + + functions.add('\"' + n + '\"'); + functions.add('\"' + n + "__VLjava_lang_Object_2Ljava_lang_Object_2" + '\"'); + } + return true; + } + + private CharSequence wrapParams( + ExecutableElement ee, String id, String className, String evName, String dataName + ) { + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); + StringBuilder params = new StringBuilder(); + boolean first = true; + for (VariableElement ve : ee.getParameters()) { + if (!first) { + params.append(", "); + } + first = false; + String toCall = null; + if (ve.asType() == stringType) { + if (ve.getSimpleName().contentEquals("id")) { + params.append('"').append(id).append('"'); + continue; + } + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString"; + } + if (ve.asType().getKind() == TypeKind.DOUBLE) { + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble"; + } + if (ve.asType().getKind() == TypeKind.INT) { + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt"; + } + + if (toCall != null) { + params.append(toCall).append('('); + if (dataName != null && ve.getSimpleName().contentEquals("data")) { + params.append(dataName); + params.append(", null"); + } else { + params.append(evName); + params.append(", \""); + params.append(ve.getSimpleName().toString()); + params.append("\""); + } + params.append(")"); + continue; + } + String rn = ve.asType().toString(); + int last = rn.lastIndexOf('.'); + if (last >= 0) { + rn = rn.substring(last + 1); + } + if (rn.equals(className)) { + params.append(className).append(".this"); + continue; + } + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@On method can only accept String named 'id' or " + className + " arguments", + ee + ); + } + return params; + } } diff -r ecbd252fd3a7 -r af170d42b5b3 javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnFunction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnFunction.java Mon Mar 25 11:50:36 2013 +0100 @@ -0,0 +1,34 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.htmlpage.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Methods in class annotated by {@link Model} or {@link Page} can be + * annotated by this annotation to signal that they should be available + * as functions to users of the model classes. + * + * @author Jaroslav Tulach + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +public @interface OnFunction { +} diff -r ecbd252fd3a7 -r af170d42b5b3 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Fri Mar 22 17:03:32 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Mon Mar 25 11:50:36 2013 +0100 @@ -21,6 +21,7 @@ import org.apidesign.bck2brwsr.core.JavaScriptBody; import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; import org.apidesign.bck2brwsr.htmlpage.api.OnEvent; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; import org.apidesign.bck2brwsr.vmtest.BrwsrTest; @@ -34,7 +35,8 @@ */ @Page(xhtml="Knockout.xhtml", className="KnockoutModel", properties={ @Property(name="name", type=String.class), - @Property(name="results", type=String.class, array = true) + @Property(name="results", type=String.class, array = true), + @Property(name="callbackCount", type=int.class) }) public class KnockoutTest { @@ -55,7 +57,7 @@ @HtmlFragment( "
    \n" - + "
  • \n" + + "
  • \n" + "
\n" ) @BrwsrTest public void displayContentOfArray() { @@ -66,10 +68,15 @@ int cnt = countChildren("ul"); assert cnt == 1 : "One child, but was " + cnt; - m.getResults().add("hello"); + m.getResults().add("Hi"); cnt = countChildren("ul"); assert cnt == 2 : "Two children now, but was " + cnt; + + triggerChildClick("ul", 1); + + assert 1 == m.getCallbackCount() : "One callback " + m.getCallbackCount(); + assert "Hi".equals(m.getName()) : "We got callback from 2nd child " + m.getName(); } @HtmlFragment( @@ -91,6 +98,12 @@ assert cnt == 2 : "Two children now, but was " + cnt; } + @OnFunction + static void call(KnockoutModel m, String data) { + m.setName(data); + m.setCallbackCount(m.getCallbackCount() + 1); + } + @ComputedProperty static String helloMessage(String name) { return "Hello " + name + "!"; @@ -112,4 +125,12 @@ + "return e.children.length;\n " ) private static native int countChildren(String id); + + @JavaScriptBody(args = { "id", "pos" }, body = + "var e = window.document.getElementById(id);\n " + + "var ev = window.document.createEvent('MouseEvents');\n " + + "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n " + + "e.children[pos].dispatchEvent(ev);\n " + ) + private static native void triggerChildClick(String id, int pos); } diff -r ecbd252fd3a7 -r af170d42b5b3 javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Fri Mar 22 17:03:32 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Mon Mar 25 11:50:36 2013 +0100 @@ -23,6 +23,7 @@ import java.util.List; import java.util.ListIterator; import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; import static org.testng.Assert.*; @@ -168,6 +169,10 @@ } } + @OnFunction + static void doSomething() { + } + @ComputedProperty static int powerValue(int value) { return value * value; diff -r ecbd252fd3a7 -r af170d42b5b3 javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java --- a/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java Fri Mar 22 17:03:32 2013 +0100 +++ b/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java Mon Mar 25 11:50:36 2013 +0100 @@ -17,9 +17,11 @@ */ package org.apidesign.bck2brwsr.demo.calc.staticcompilation; +import java.util.List; import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; import org.apidesign.bck2brwsr.htmlpage.api.On; import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; @@ -38,7 +40,7 @@ }) public class Calc { static { - new Calculator().applyBindings(); + new Calculator().applyBindings().setOperation("plus"); } @On(event = CLICK, id="clear") @@ -76,6 +78,16 @@ c.setMemory(0); } + @OnFunction + static void recoverMemory(Calculator c, double data) { + c.setDisplay(data); + } + + @OnFunction + static void removeMemory(Calculator c, double data) { + c.getHistory().remove(data); + } + private static double compute(String op, double memory, double display) { switch (op) { case "plus": return memory + display; @@ -112,4 +124,9 @@ } return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display); } + + @ComputedProperty + static boolean emptyHistory(List history) { + return history.isEmpty(); + } } diff -r ecbd252fd3a7 -r af170d42b5b3 javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml --- a/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml Fri Mar 22 17:03:32 2013 +0100 +++ b/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml Mon Mar 25 11:50:36 2013 +0100 @@ -79,8 +79,13 @@

Previous Results

+
No results yet.