Merge of recent work on model. @Model classes can have @OnFunction methods and their properties are accessible from the knockout bindings
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Wed, 03 Apr 2013 09:17:58 +0200
changeset 91709fee723d658
parent 916 c3c30f25c723
parent 915 b219134a2782
child 918 cee3a4e6872e
child 922 2fb3e929962f
Merge of recent work on model. @Model classes can have @OnFunction methods and their properties are accessible from the knockout bindings
     1.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java	Tue Apr 02 15:52:25 2013 +0200
     1.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java	Wed Apr 03 09:17:58 2013 +0200
     1.3 @@ -41,6 +41,14 @@
     1.4          Object ret = getProperty(object, property);
     1.5          return ret instanceof Number ? ((Number)ret).intValue() : Integer.MIN_VALUE;
     1.6      }
     1.7 +
     1.8 +    public static <T> T toModel(Class<T> modelClass, Object object, String property) {
     1.9 +        Object ret = getProperty(object, property);
    1.10 +        if (ret == null || modelClass.isInstance(ret)) {
    1.11 +            return modelClass.cast(ret);
    1.12 +        }
    1.13 +        throw new IllegalStateException("Value " + ret + " is not of type " + modelClass);
    1.14 +    }
    1.15      
    1.16      @JavaScriptBody(args = { "object", "property" },
    1.17          body = "if (property === null) return object;\n"
     2.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java	Tue Apr 02 15:52:25 2013 +0200
     2.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java	Wed Apr 03 09:17:58 2013 +0200
     2.3 @@ -30,45 +30,40 @@
     2.4  public class Knockout {
     2.5      /** used by tests */
     2.6      static Knockout next;
     2.7 +    private final Object model;
     2.8  
     2.9 -    Knockout() {
    2.10 +    Knockout(Object model) {
    2.11 +        this.model = model == null ? this : model;
    2.12      }
    2.13      
    2.14      public static <M> Knockout applyBindings(
    2.15 +        Object model, String[] propsGettersAndSetters,
    2.16 +        String[] methodsAndSignatures
    2.17 +    ) {
    2.18 +        applyImpl(propsGettersAndSetters, model.getClass(), model, model, methodsAndSignatures);
    2.19 +        return new Knockout(model);
    2.20 +    }
    2.21 +    public static <M> Knockout applyBindings(
    2.22          Class<M> modelClass, M model, String[] propsGettersAndSetters,
    2.23          String[] methodsAndSignatures
    2.24      ) {
    2.25          Knockout bindings = next;
    2.26          next = null;
    2.27          if (bindings == null) {
    2.28 -            bindings = new Knockout();
    2.29 +            bindings = new Knockout(null);
    2.30          }
    2.31 -        for (int i = 0; i < propsGettersAndSetters.length; i += 4) {
    2.32 -            try {
    2.33 -                Method getter = modelClass.getMethod(propsGettersAndSetters[i + 3]);
    2.34 -                bind(bindings, model, propsGettersAndSetters[i],
    2.35 -                    propsGettersAndSetters[i + 1],
    2.36 -                    propsGettersAndSetters[i + 2],
    2.37 -                    getter.getReturnType().isPrimitive(),
    2.38 -                    List.class.isAssignableFrom(getter.getReturnType())
    2.39 -                );
    2.40 -            } catch (NoSuchMethodException ex) {
    2.41 -                throw new IllegalStateException(ex.getMessage());
    2.42 -            }
    2.43 -        }
    2.44 -        for (int i = 0; i < methodsAndSignatures.length; i += 2) {
    2.45 -            expose(
    2.46 -                bindings, model, methodsAndSignatures[i], methodsAndSignatures[i + 1]
    2.47 -            );
    2.48 -        }
    2.49 +        applyImpl(propsGettersAndSetters, modelClass, bindings, model, methodsAndSignatures);
    2.50          applyBindings(bindings);
    2.51          return bindings;
    2.52      }
    2.53  
    2.54 -    @JavaScriptBody(args = { "prop" }, body =
    2.55 -        "this[prop].valueHasMutated();"
    2.56 +    public void valueHasMutated(String prop) {
    2.57 +        valueHasMutated(model, prop);
    2.58 +    }
    2.59 +    @JavaScriptBody(args = { "self", "prop" }, body =
    2.60 +        "self[prop].valueHasMutated();"
    2.61      )
    2.62 -    public void valueHasMutated(String prop) {
    2.63 +    public void valueHasMutated(Object self, String prop) {
    2.64      }
    2.65      
    2.66  
    2.67 @@ -107,4 +102,29 @@
    2.68      
    2.69      @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
    2.70      private static void applyBindings(Object bindings) {}
    2.71 +    
    2.72 +    private static void applyImpl(
    2.73 +        String[] propsGettersAndSetters,
    2.74 +        Class<?> modelClass,
    2.75 +        Object bindings,
    2.76 +        Object model,
    2.77 +        String[] methodsAndSignatures
    2.78 +    ) throws IllegalStateException, SecurityException {
    2.79 +        for (int i = 0; i < propsGettersAndSetters.length; i += 4) {
    2.80 +            try {
    2.81 +                Method getter = modelClass.getMethod(propsGettersAndSetters[i + 3]);
    2.82 +                bind(bindings, model, propsGettersAndSetters[i],
    2.83 +                    propsGettersAndSetters[i + 1],
    2.84 +                    propsGettersAndSetters[i + 2],
    2.85 +                    getter.getReturnType().isPrimitive(),
    2.86 +                    List.class.isAssignableFrom(getter.getReturnType()));
    2.87 +            } catch (NoSuchMethodException ex) {
    2.88 +                throw new IllegalStateException(ex.getMessage());
    2.89 +            }
    2.90 +        }
    2.91 +        for (int i = 0; i < methodsAndSignatures.length; i += 2) {
    2.92 +            expose(
    2.93 +                bindings, model, methodsAndSignatures[i], methodsAndSignatures[i + 1]);
    2.94 +        }
    2.95 +    }
    2.96  }
     3.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Tue Apr 02 15:52:25 2013 +0200
     3.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Wed Apr 03 09:17:58 2013 +0200
     3.3 @@ -28,9 +28,9 @@
     3.4  import java.util.HashMap;
     3.5  import java.util.LinkedHashSet;
     3.6  import java.util.List;
     3.7 -import java.util.Locale;
     3.8  import java.util.Map;
     3.9  import java.util.Set;
    3.10 +import java.util.WeakHashMap;
    3.11  import javax.annotation.processing.AbstractProcessor;
    3.12  import javax.annotation.processing.Completion;
    3.13  import javax.annotation.processing.Completions;
    3.14 @@ -73,6 +73,7 @@
    3.15      "org.apidesign.bck2brwsr.htmlpage.api.On"
    3.16  })
    3.17  public final class PageProcessor extends AbstractProcessor {
    3.18 +    private final Map<Element,String> models = new WeakHashMap<>();
    3.19      @Override
    3.20      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    3.21          boolean ok = true;
    3.22 @@ -86,6 +87,9 @@
    3.23                  ok = false;
    3.24              }
    3.25          }
    3.26 +        if (roundEnv.processingOver()) {
    3.27 +            models.clear();
    3.28 +        }
    3.29          return ok;
    3.30      }
    3.31  
    3.32 @@ -111,6 +115,7 @@
    3.33          try {
    3.34              StringWriter body = new StringWriter();
    3.35              List<String> propsGetSet = new ArrayList<>();
    3.36 +            List<String> functions = new ArrayList<>();
    3.37              Map<String, Collection<String>> propsDeps = new HashMap<>();
    3.38              if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
    3.39                  ok = false;
    3.40 @@ -118,17 +123,29 @@
    3.41              if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps)) {
    3.42                  ok = false;
    3.43              }
    3.44 +            if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
    3.45 +                ok = false;
    3.46 +            }
    3.47              FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
    3.48              w = new OutputStreamWriter(java.openOutputStream());
    3.49              try {
    3.50                  w.append("package " + pkg + ";\n");
    3.51                  w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
    3.52                  w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
    3.53 +                w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
    3.54                  w.append("final class ").append(className).append(" {\n");
    3.55                  w.append("  private Object json;\n");
    3.56                  w.append("  private boolean locked;\n");
    3.57                  w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
    3.58                  w.append(body.toString());
    3.59 +                w.append("  private static Class<" + e.getSimpleName() + "> modelFor() { return null; }\n");
    3.60 +                w.append("  public ").append(className).append("() {\n");
    3.61 +                w.append("    ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
    3.62 +                writeStringArray(propsGetSet, w);
    3.63 +                w.append(", ");
    3.64 +                writeStringArray(functions, w);
    3.65 +                w.append("    );\n");
    3.66 +                w.append("  };\n");
    3.67                  w.append("}\n");
    3.68              } finally {
    3.69                  w.close();
    3.70 @@ -203,25 +220,10 @@
    3.71                      w.write("public " + className + " applyBindings() {\n");
    3.72                      w.write("  ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
    3.73                      w.write(className + ".class, this, ");
    3.74 -                    w.write("new String[] {\n");
    3.75 -                    String sep = "";
    3.76 -                    for (String n : propsGetSet) {
    3.77 -                        w.write(sep);
    3.78 -                        if (n == null) {
    3.79 -                            w.write("    null");
    3.80 -                        } else {
    3.81 -                            w.write("    \"" + n + "\"");
    3.82 -                        }
    3.83 -                        sep = ",\n";
    3.84 -                    }
    3.85 -                    w.write("\n  }, new String[] {\n");
    3.86 -                    sep = "";
    3.87 -                    for (String n : functions) {
    3.88 -                        w.write(sep);
    3.89 -                        w.write(n);
    3.90 -                        sep = ",\n";
    3.91 -                    }
    3.92 -                    w.write("\n  });\n  return this;\n}\n");
    3.93 +                    writeStringArray(propsGetSet, w);
    3.94 +                    w.append(", ");
    3.95 +                    writeStringArray(functions, w);
    3.96 +                    w.write(");\n  return this;\n}\n");
    3.97  
    3.98                      w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
    3.99                      w.write("  org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
   3.100 @@ -537,6 +539,7 @@
   3.101      private String typeName(Element where, Property p) {
   3.102          String ret;
   3.103          boolean isModel = false;
   3.104 +        boolean isEnum = false;
   3.105          try {
   3.106              ret = p.type().getName();
   3.107          } catch (MirroredTypeException ex) {
   3.108 @@ -546,9 +549,13 @@
   3.109              if (m != null) {
   3.110                  ret = findPkgName(e) + '.' + m.className();
   3.111                  isModel = true;
   3.112 +                models.put(e, m.className());
   3.113              } else {
   3.114                  ret = tm.toString();
   3.115              }
   3.116 +            TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
   3.117 +            enm = processingEnv.getTypeUtils().erasure(enm);
   3.118 +            isEnum = processingEnv.getTypeUtils().isSubtype(tm, enm);
   3.119          }
   3.120          if (p.array()) {
   3.121              String bt = findBoxedType(ret);
   3.122 @@ -556,7 +563,7 @@
   3.123                  return bt;
   3.124              }
   3.125          }
   3.126 -        if (!isModel && !"java.lang.String".equals(ret)) {
   3.127 +        if (!isModel && !"java.lang.String".equals(ret) && !isEnum) {
   3.128              String bt = findBoxedType(ret);
   3.129              if (bt == null) {
   3.130                  processingEnv.getMessager().printMessage(
   3.131 @@ -664,8 +671,8 @@
   3.132              body.append(");\n");
   3.133              body.append("}\n");
   3.134              
   3.135 -            functions.add('\"' + n + '\"');
   3.136 -            functions.add('\"' + n + "__VLjava_lang_Object_2Ljava_lang_Object_2" + '\"');
   3.137 +            functions.add(n);
   3.138 +            functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
   3.139          }
   3.140          return true;
   3.141      }
   3.142 @@ -687,18 +694,21 @@
   3.143                      params.append('"').append(id).append('"');
   3.144                      continue;
   3.145                  }
   3.146 -                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString";
   3.147 +                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(";
   3.148              }
   3.149              if (ve.asType().getKind() == TypeKind.DOUBLE) {
   3.150 -                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble";
   3.151 +                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(";
   3.152              }
   3.153              if (ve.asType().getKind() == TypeKind.INT) {
   3.154 -                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt";
   3.155 +                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt(";
   3.156 +            }
   3.157 +            if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
   3.158 +                toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, ";
   3.159              }
   3.160  
   3.161              if (toCall != null) {
   3.162 -                params.append(toCall).append('(');
   3.163 -                if (dataName != null && ve.getSimpleName().contentEquals("data")) {
   3.164 +                params.append(toCall);
   3.165 +                if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
   3.166                      params.append(dataName);
   3.167                      params.append(", null");
   3.168                  } else {
   3.169 @@ -726,4 +736,35 @@
   3.170          }
   3.171          return params;
   3.172      }
   3.173 +    
   3.174 +    private boolean isModel(TypeMirror tm) {
   3.175 +        final Element e = processingEnv.getTypeUtils().asElement(tm);
   3.176 +        if (e == null) {
   3.177 +            return false;
   3.178 +        }
   3.179 +        for (Element ch : e.getEnclosedElements()) {
   3.180 +            if (ch.getKind() == ElementKind.METHOD) {
   3.181 +                ExecutableElement ee = (ExecutableElement)ch;
   3.182 +                if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
   3.183 +                    return true;
   3.184 +                }
   3.185 +            }
   3.186 +        }
   3.187 +        return models.values().contains(e.getSimpleName().toString());
   3.188 +    }
   3.189 +
   3.190 +    private void writeStringArray(List<String> strings, Writer w) throws IOException {
   3.191 +        w.write("new String[] {\n");
   3.192 +        String sep = "";
   3.193 +        for (String n : strings) {
   3.194 +            w.write(sep);
   3.195 +            if (n == null) {
   3.196 +                w.write("    null");
   3.197 +            } else {
   3.198 +                w.write("    \"" + n + "\"");
   3.199 +            }
   3.200 +            sep = ",\n";
   3.201 +        }
   3.202 +        w.write("\n  }");
   3.203 +    }
   3.204  }
     4.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java	Tue Apr 02 15:52:25 2013 +0200
     4.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java	Wed Apr 03 09:17:58 2013 +0200
     4.3 @@ -36,7 +36,8 @@
     4.4  @Page(xhtml="Knockout.xhtml", className="KnockoutModel", properties={
     4.5      @Property(name="name", type=String.class),
     4.6      @Property(name="results", type=String.class, array = true),
     4.7 -    @Property(name="callbackCount", type=int.class)
     4.8 +    @Property(name="callbackCount", type=int.class),
     4.9 +    @Property(name="people", type=PersonImpl.class, array = true)
    4.10  }) 
    4.11  public class KnockoutTest {
    4.12      
    4.13 @@ -98,11 +99,83 @@
    4.14          assert cnt == 2 : "Two children now, but was " + cnt;
    4.15      }
    4.16      
    4.17 +    @HtmlFragment(
    4.18 +        "<ul id='ul' data-bind='foreach: people'>\n"
    4.19 +        + "  <li data-bind='text: $data.firstName, click: $root.removePerson'></li>\n"
    4.20 +        + "</ul>\n"
    4.21 +    )
    4.22 +    @BrwsrTest public void displayContentOfArrayOfPeople() {
    4.23 +        KnockoutModel m = new KnockoutModel();
    4.24 +        
    4.25 +        final Person first = new Person();
    4.26 +        first.setFirstName("first");
    4.27 +        m.getPeople().add(first);
    4.28 +        
    4.29 +        m.applyBindings();
    4.30 +        
    4.31 +        int cnt = countChildren("ul");
    4.32 +        assert cnt == 1 : "One child, but was " + cnt;
    4.33 +        
    4.34 +        final Person second = new Person();
    4.35 +        second.setFirstName("second");
    4.36 +        m.getPeople().add(second);
    4.37 +
    4.38 +        cnt = countChildren("ul");
    4.39 +        assert cnt == 2 : "Two children now, but was " + cnt;
    4.40 +
    4.41 +        triggerChildClick("ul", 1);
    4.42 +        
    4.43 +        assert 1 == m.getCallbackCount() : "One callback " + m.getCallbackCount();
    4.44 +
    4.45 +        cnt = countChildren("ul");
    4.46 +        assert cnt == 1 : "Again one child, but was " + cnt;
    4.47 +        
    4.48 +        String txt = childText("ul", 0);
    4.49 +        assert "first".equals(txt) : "Expecting 'first': " + txt;
    4.50 +        
    4.51 +        first.setFirstName("changed");
    4.52 +        
    4.53 +        txt = childText("ul", 0);
    4.54 +        assert "changed".equals(txt) : "Expecting 'changed': " + txt;
    4.55 +    }
    4.56 +    
    4.57 +    @HtmlFragment(
    4.58 +        "<ul id='ul' data-bind='foreach: people'>\n"
    4.59 +        + "  <li data-bind='text: $data.firstName, click: changeSex'></li>\n"
    4.60 +        + "</ul>\n"
    4.61 +    )
    4.62 +    @BrwsrTest public void onPersonFunction() {
    4.63 +        KnockoutModel m = new KnockoutModel();
    4.64 +        
    4.65 +        final Person first = new Person();
    4.66 +        first.setFirstName("first");
    4.67 +        first.setSex(Sex.MALE);
    4.68 +        m.getPeople().add(first);
    4.69 +        
    4.70 +        
    4.71 +        m.applyBindings();
    4.72 +        
    4.73 +        int cnt = countChildren("ul");
    4.74 +        assert cnt == 1 : "One child, but was " + cnt;
    4.75 +        
    4.76 +        
    4.77 +        triggerChildClick("ul", 0);
    4.78 +        
    4.79 +        assert first.getSex() == Sex.FEMALE : "Transverted to female: " + first.getSex();
    4.80 +    }
    4.81 +     
    4.82      @OnFunction
    4.83      static void call(KnockoutModel m, String data) {
    4.84          m.setName(data);
    4.85          m.setCallbackCount(m.getCallbackCount() + 1);
    4.86      }
    4.87 +
    4.88 +    @OnFunction
    4.89 +    static void removePerson(KnockoutModel model, Person data) {
    4.90 +        model.setCallbackCount(model.getCallbackCount() + 1);
    4.91 +        model.getPeople().remove(data);
    4.92 +    }
    4.93 +    
    4.94      
    4.95      @ComputedProperty
    4.96      static String helloMessage(String name) {
    4.97 @@ -133,4 +206,11 @@
    4.98          + "e.children[pos].dispatchEvent(ev);\n "
    4.99      )
   4.100      private static native void triggerChildClick(String id, int pos);
   4.101 +
   4.102 +    @JavaScriptBody(args = { "id", "pos" }, body = 
   4.103 +          "var e = window.document.getElementById(id);\n "
   4.104 +        + "var t = e.children[pos].innerHTML;\n "
   4.105 +        + "return t ? t : null;"
   4.106 +    )
   4.107 +    private static native String childText(String id, int pos);
   4.108  }
     5.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Tue Apr 02 15:52:25 2013 +0200
     5.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Wed Apr 03 09:17:58 2013 +0200
     5.3 @@ -197,6 +197,10 @@
     5.4      static class MockKnockout extends Knockout {
     5.5          List<String> mutated = new ArrayList<>();
     5.6          
     5.7 +        MockKnockout() {
     5.8 +            super(null);
     5.9 +        }
    5.10 +        
    5.11          @Override
    5.12          public void valueHasMutated(String prop) {
    5.13              mutated.add(prop);
     6.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java	Tue Apr 02 15:52:25 2013 +0200
     6.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java	Wed Apr 03 09:17:58 2013 +0200
     6.3 @@ -19,6 +19,7 @@
     6.4  
     6.5  import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
     6.6  import org.apidesign.bck2brwsr.htmlpage.api.Model;
     6.7 +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
     6.8  import org.apidesign.bck2brwsr.htmlpage.api.Property;
     6.9  
    6.10  /**
    6.11 @@ -28,7 +29,7 @@
    6.12  @Model(className = "Person", properties = {
    6.13      @Property(name = "firstName", type = String.class),
    6.14      @Property(name = "lastName", type = String.class),
    6.15 -    @Property(name = "male", type = boolean.class)
    6.16 +    @Property(name = "sex", type = Sex.class)
    6.17  })
    6.18  final class PersonImpl {
    6.19      @ComputedProperty 
    6.20 @@ -37,7 +38,16 @@
    6.21      }
    6.22      
    6.23      @ComputedProperty
    6.24 -    public static String sex(boolean male) {
    6.25 -        return male ? "Male" : "Female";
    6.26 +    public static String sexType(Sex sex) {
    6.27 +        return sex == null ? "unknown" : sex.toString();
    6.28 +    }
    6.29 +    
    6.30 +    @OnFunction
    6.31 +    static void changeSex(Person p) {
    6.32 +        if (p.getSex() == Sex.MALE) {
    6.33 +            p.setSex(Sex.FEMALE);
    6.34 +        } else {
    6.35 +            p.setSex(Sex.MALE);
    6.36 +        }
    6.37      }
    6.38  }
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Sex.java	Wed Apr 03 09:17:58 2013 +0200
     7.3 @@ -0,0 +1,26 @@
     7.4 +/**
     7.5 + * Back 2 Browser Bytecode Translator
     7.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     7.7 + *
     7.8 + * This program is free software: you can redistribute it and/or modify
     7.9 + * it under the terms of the GNU General Public License as published by
    7.10 + * the Free Software Foundation, version 2 of the License.
    7.11 + *
    7.12 + * This program is distributed in the hope that it will be useful,
    7.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    7.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    7.15 + * GNU General Public License for more details.
    7.16 + *
    7.17 + * You should have received a copy of the GNU General Public License
    7.18 + * along with this program. Look for COPYING file in the top folder.
    7.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
    7.20 + */
    7.21 +package org.apidesign.bck2brwsr.htmlpage;
    7.22 +
    7.23 +/**
    7.24 + *
    7.25 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    7.26 + */
    7.27 +public enum Sex {
    7.28 +    MALE, FEMALE;
    7.29 +}
     8.1 --- a/javaquery/demo-calculator-dynamic/nbactions.xml	Tue Apr 02 15:52:25 2013 +0200
     8.2 +++ b/javaquery/demo-calculator-dynamic/nbactions.xml	Wed Apr 03 09:17:58 2013 +0200
     8.3 @@ -23,7 +23,7 @@
     8.4              <actionName>run</actionName>
     8.5              <goals>
     8.6                  <goal>process-classes</goal>
     8.7 -                <goal>org.apidesign.bck2brwsr:mojo:0.5-SNAPSHOT:brwsr</goal>
     8.8 +                <goal>org.apidesign.bck2brwsr:mojo:0.6-SNAPSHOT:brwsr</goal>
     8.9              </goals>
    8.10          </action>
    8.11      </actions>
     9.1 --- a/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java	Tue Apr 02 15:52:25 2013 +0200
     9.2 +++ b/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java	Wed Apr 03 09:17:58 2013 +0200
     9.3 @@ -36,7 +36,7 @@
     9.4      @Property(name = "display", type = double.class),
     9.5      @Property(name = "operation", type = String.class),
     9.6      @Property(name = "hover", type = boolean.class),
     9.7 -    @Property(name = "history", type = double.class, array = true)
     9.8 +    @Property(name = "history", type = HistoryImpl.class, array = true)
     9.9  })
    9.10  public class Calc {
    9.11      static {
    9.12 @@ -74,19 +74,22 @@
    9.13              c.getDisplay()
    9.14          );
    9.15          c.setDisplay(newValue);
    9.16 -        if (!c.getHistory().contains(newValue)) {
    9.17 -            c.getHistory().add(newValue);
    9.18 +        if (!containsValue(c.getHistory(), newValue)) {
    9.19 +            History h = new History();
    9.20 +            h.setValue(newValue);
    9.21 +            h.setOperation(c.getOperation());
    9.22 +            c.getHistory().add(h);
    9.23          }
    9.24          c.setMemory(0);
    9.25      }
    9.26      
    9.27      @OnFunction
    9.28 -    static void recoverMemory(Calculator c, double data) {
    9.29 -        c.setDisplay(data);
    9.30 +    static void recoverMemory(Calculator c, History data) {
    9.31 +        c.setDisplay(data.getValue());
    9.32      }
    9.33  
    9.34      @OnFunction
    9.35 -    static void removeMemory(Calculator c, double data) {
    9.36 +    static void removeMemory(Calculator c, History data) {
    9.37          c.getHistory().remove(data);
    9.38      }
    9.39      
    9.40 @@ -131,4 +134,13 @@
    9.41      static boolean emptyHistory(List<?> history) {
    9.42          return history.isEmpty();
    9.43      }
    9.44 +
    9.45 +    private static boolean containsValue(List<History> arr, final double newValue) {
    9.46 +        for (History history : arr) {
    9.47 +            if (history.getValue() == newValue) {
    9.48 +                return true;
    9.49 +            }
    9.50 +        }
    9.51 +        return false;
    9.52 +    }
    9.53  }
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/HistoryImpl.java	Wed Apr 03 09:17:58 2013 +0200
    10.3 @@ -0,0 +1,43 @@
    10.4 +/**
    10.5 + * Back 2 Browser Bytecode Translator
    10.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    10.7 + *
    10.8 + * This program is free software: you can redistribute it and/or modify
    10.9 + * it under the terms of the GNU General Public License as published by
   10.10 + * the Free Software Foundation, version 2 of the License.
   10.11 + *
   10.12 + * This program is distributed in the hope that it will be useful,
   10.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
   10.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   10.15 + * GNU General Public License for more details.
   10.16 + *
   10.17 + * You should have received a copy of the GNU General Public License
   10.18 + * along with this program. Look for COPYING file in the top folder.
   10.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
   10.20 + */
   10.21 +package org.apidesign.bck2brwsr.demo.calc;
   10.22 +
   10.23 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
   10.24 +import org.apidesign.bck2brwsr.htmlpage.api.Model;
   10.25 +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
   10.26 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
   10.27 +
   10.28 +/**
   10.29 + *
   10.30 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   10.31 + */
   10.32 +@Model(className = "History", properties = {
   10.33 +    @Property(name = "value", type = double.class),
   10.34 +    @Property(name = "operation", type = String.class)
   10.35 +})
   10.36 +public class HistoryImpl {
   10.37 +    @ComputedProperty
   10.38 +    static String resultOf(String operation) {
   10.39 +        return "result of " + operation;
   10.40 +    }
   10.41 +    
   10.42 +    @OnFunction
   10.43 +    static void twice(History data) {
   10.44 +        data.setValue(2.0 * data.getValue());
   10.45 +    }
   10.46 +}
    11.1 --- a/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml	Tue Apr 02 15:52:25 2013 +0200
    11.2 +++ b/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml	Wed Apr 03 09:17:58 2013 +0200
    11.3 @@ -83,9 +83,11 @@
    11.4          <div data-bind="if: emptyHistory">No results yet.</div>
    11.5          <ul data-bind="foreach: history">
    11.6              <li>
    11.7 -                <span data-bind="text: $data"></span> -
    11.8 +                <span data-bind="text: $data.value"></span> -
    11.9                  <a href="#" data-bind="click: $root.recoverMemory">Use</a>
   11.10                  <a href="#" data-bind="click: $root.removeMemory">Remove</a>
   11.11 +                <a href="#" data-bind="click: $data.twice">Double</a> -
   11.12 +                <span data-bind="text: $data.resultOf"></span>
   11.13              </li>
   11.14          </ul>
   11.15