Mutable properties on model classes model
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Sun, 31 Mar 2013 12:01:38 +0200
branchmodel
changeset 909e51a474fcf79
parent 908 3e023bea2da4
child 910 52cb50cea1df
Mutable properties on model classes
javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java
javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java
javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java
     1.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java	Sun Mar 31 06:46:25 2013 +0200
     1.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java	Sun Mar 31 12:01:38 2013 +0200
     1.3 @@ -30,45 +30,40 @@
     1.4  public class Knockout {
     1.5      /** used by tests */
     1.6      static Knockout next;
     1.7 +    private final Object model;
     1.8  
     1.9 -    Knockout() {
    1.10 +    Knockout(Object model) {
    1.11 +        this.model = model == null ? this : model;
    1.12      }
    1.13      
    1.14      public static <M> Knockout applyBindings(
    1.15 +        Object model, String[] propsGettersAndSetters,
    1.16 +        String[] methodsAndSignatures
    1.17 +    ) {
    1.18 +        applyImpl(propsGettersAndSetters, model.getClass(), model, model, methodsAndSignatures);
    1.19 +        return new Knockout(model);
    1.20 +    }
    1.21 +    public static <M> Knockout applyBindings(
    1.22          Class<M> modelClass, M model, String[] propsGettersAndSetters,
    1.23          String[] methodsAndSignatures
    1.24      ) {
    1.25          Knockout bindings = next;
    1.26          next = null;
    1.27          if (bindings == null) {
    1.28 -            bindings = new Knockout();
    1.29 +            bindings = new Knockout(null);
    1.30          }
    1.31 -        for (int i = 0; i < propsGettersAndSetters.length; i += 4) {
    1.32 -            try {
    1.33 -                Method getter = modelClass.getMethod(propsGettersAndSetters[i + 3]);
    1.34 -                bind(bindings, model, propsGettersAndSetters[i],
    1.35 -                    propsGettersAndSetters[i + 1],
    1.36 -                    propsGettersAndSetters[i + 2],
    1.37 -                    getter.getReturnType().isPrimitive(),
    1.38 -                    List.class.isAssignableFrom(getter.getReturnType())
    1.39 -                );
    1.40 -            } catch (NoSuchMethodException ex) {
    1.41 -                throw new IllegalStateException(ex.getMessage());
    1.42 -            }
    1.43 -        }
    1.44 -        for (int i = 0; i < methodsAndSignatures.length; i += 2) {
    1.45 -            expose(
    1.46 -                bindings, model, methodsAndSignatures[i], methodsAndSignatures[i + 1]
    1.47 -            );
    1.48 -        }
    1.49 +        applyImpl(propsGettersAndSetters, modelClass, bindings, model, methodsAndSignatures);
    1.50          applyBindings(bindings);
    1.51          return bindings;
    1.52      }
    1.53  
    1.54 -    @JavaScriptBody(args = { "prop" }, body =
    1.55 -        "this[prop].valueHasMutated();"
    1.56 +    public void valueHasMutated(String prop) {
    1.57 +        valueHasMutated(model, prop);
    1.58 +    }
    1.59 +    @JavaScriptBody(args = { "self", "prop" }, body =
    1.60 +        "self[prop].valueHasMutated();"
    1.61      )
    1.62 -    public void valueHasMutated(String prop) {
    1.63 +    public void valueHasMutated(Object self, String prop) {
    1.64      }
    1.65      
    1.66  
    1.67 @@ -107,4 +102,29 @@
    1.68      
    1.69      @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
    1.70      private static void applyBindings(Object bindings) {}
    1.71 +    
    1.72 +    private static void applyImpl(
    1.73 +        String[] propsGettersAndSetters,
    1.74 +        Class<?> modelClass,
    1.75 +        Object bindings,
    1.76 +        Object model,
    1.77 +        String[] methodsAndSignatures
    1.78 +    ) throws IllegalStateException, SecurityException {
    1.79 +        for (int i = 0; i < propsGettersAndSetters.length; i += 4) {
    1.80 +            try {
    1.81 +                Method getter = modelClass.getMethod(propsGettersAndSetters[i + 3]);
    1.82 +                bind(bindings, model, propsGettersAndSetters[i],
    1.83 +                    propsGettersAndSetters[i + 1],
    1.84 +                    propsGettersAndSetters[i + 2],
    1.85 +                    getter.getReturnType().isPrimitive(),
    1.86 +                    List.class.isAssignableFrom(getter.getReturnType()));
    1.87 +            } catch (NoSuchMethodException ex) {
    1.88 +                throw new IllegalStateException(ex.getMessage());
    1.89 +            }
    1.90 +        }
    1.91 +        for (int i = 0; i < methodsAndSignatures.length; i += 2) {
    1.92 +            expose(
    1.93 +                bindings, model, methodsAndSignatures[i], methodsAndSignatures[i + 1]);
    1.94 +        }
    1.95 +    }
    1.96  }
     2.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Sun Mar 31 06:46:25 2013 +0200
     2.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java	Sun Mar 31 12:01:38 2013 +0200
     2.3 @@ -135,16 +135,13 @@
     2.4                  w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
     2.5                  w.append(body.toString());
     2.6                  w.append("  private static Class<" + e.getSimpleName() + "> modelFor() { return null; }\n");
     2.7 -                for (int i = 0; i < propsGetSet.size(); i += 4) {
     2.8 -                    w.append("  @JavaScriptOnly(name=\"" + propsGetSet.get(i) + "\",\n");
     2.9 -                    w.append("    value=\"function() { ");
    2.10 -                    final String setter = propsGetSet.get(i + 2);
    2.11 -                    if (setter != null) {
    2.12 -                        w.append("if (arguments.length == 1) this." + setter + "(arguments[0]); ");
    2.13 -                    }
    2.14 -                    w.append("return this." + propsGetSet.get(i + 1) + "();}\")\n");
    2.15 -                    w.append("  private static native void __accessor" + i + "();");
    2.16 -                }
    2.17 +                w.append("  public ").append(className).append("() {\n");
    2.18 +                w.append("    ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
    2.19 +                writeStringArray(propsGetSet, w);
    2.20 +                w.append(", ");
    2.21 +                writeStringArray(Collections.<String>emptyList(), w);
    2.22 +                w.append("    );\n");
    2.23 +                w.append("  };\n");
    2.24                  w.append("}\n");
    2.25              } finally {
    2.26                  w.close();
    2.27 @@ -219,25 +216,10 @@
    2.28                      w.write("public " + className + " applyBindings() {\n");
    2.29                      w.write("  ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
    2.30                      w.write(className + ".class, this, ");
    2.31 -                    w.write("new String[] {\n");
    2.32 -                    String sep = "";
    2.33 -                    for (String n : propsGetSet) {
    2.34 -                        w.write(sep);
    2.35 -                        if (n == null) {
    2.36 -                            w.write("    null");
    2.37 -                        } else {
    2.38 -                            w.write("    \"" + n + "\"");
    2.39 -                        }
    2.40 -                        sep = ",\n";
    2.41 -                    }
    2.42 -                    w.write("\n  }, new String[] {\n");
    2.43 -                    sep = "";
    2.44 -                    for (String n : functions) {
    2.45 -                        w.write(sep);
    2.46 -                        w.write(n);
    2.47 -                        sep = ",\n";
    2.48 -                    }
    2.49 -                    w.write("\n  });\n  return this;\n}\n");
    2.50 +                    writeStringArray(propsGetSet, w);
    2.51 +                    w.append(", ");
    2.52 +                    writeStringArray(functions, w);
    2.53 +                    w.write(");\n  return this;\n}\n");
    2.54  
    2.55                      w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
    2.56                      w.write("  org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
    2.57 @@ -681,8 +663,8 @@
    2.58              body.append(");\n");
    2.59              body.append("}\n");
    2.60              
    2.61 -            functions.add('\"' + n + '\"');
    2.62 -            functions.add('\"' + n + "__VLjava_lang_Object_2Ljava_lang_Object_2" + '\"');
    2.63 +            functions.add(n);
    2.64 +            functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
    2.65          }
    2.66          return true;
    2.67      }
    2.68 @@ -762,4 +744,19 @@
    2.69          }
    2.70          return models.values().contains(e.getSimpleName().toString());
    2.71      }
    2.72 +
    2.73 +    private void writeStringArray(List<String> strings, Writer w) throws IOException {
    2.74 +        w.write("new String[] {\n");
    2.75 +        String sep = "";
    2.76 +        for (String n : strings) {
    2.77 +            w.write(sep);
    2.78 +            if (n == null) {
    2.79 +                w.write("    null");
    2.80 +            } else {
    2.81 +                w.write("    \"" + n + "\"");
    2.82 +            }
    2.83 +            sep = ",\n";
    2.84 +        }
    2.85 +        w.write("\n  }");
    2.86 +    }
    2.87  }
     3.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java	Sun Mar 31 06:46:25 2013 +0200
     3.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java	Sun Mar 31 12:01:38 2013 +0200
     3.3 @@ -101,7 +101,7 @@
     3.4      
     3.5      @HtmlFragment(
     3.6          "<ul id='ul' data-bind='foreach: people'>\n"
     3.7 -        + "  <li data-bind='text: $data.firstName(), click: $root.removePerson'></li>\n"
     3.8 +        + "  <li data-bind='text: $data.firstName, click: $root.removePerson'></li>\n"
     3.9          + "</ul>\n"
    3.10      )
    3.11      @BrwsrTest public void displayContentOfArrayOfPeople() {
    3.12 @@ -132,6 +132,11 @@
    3.13          
    3.14          String txt = childText("ul", 0);
    3.15          assert "first".equals(txt) : "Expecting 'first': " + txt;
    3.16 +        
    3.17 +        first.setFirstName("changed");
    3.18 +        
    3.19 +        txt = childText("ul", 0);
    3.20 +        assert "changed".equals(txt) : "Expecting 'changed': " + txt;
    3.21      }
    3.22       
    3.23      @OnFunction
     4.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Sun Mar 31 06:46:25 2013 +0200
     4.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java	Sun Mar 31 12:01:38 2013 +0200
     4.3 @@ -197,6 +197,10 @@
     4.4      static class MockKnockout extends Knockout {
     4.5          List<String> mutated = new ArrayList<>();
     4.6          
     4.7 +        MockKnockout() {
     4.8 +            super(null);
     4.9 +        }
    4.10 +        
    4.11          @Override
    4.12          public void valueHasMutated(String prop) {
    4.13              mutated.add(prop);