\n"
+ );
+ try {
+ KnockoutModel m = new KnockoutModel();
+ m.getPeople().add(new Person());
+
+ m = Models.bind(m, newContext());
+ m.getFirstPerson().setFirstName("Jarda");
+ m.getFirstPerson().setLastName("Tulach");
+ m.applyBindings();
+
+ String v = getSetInput(null);
+ assertEquals("Jarda Tulach", v, "Value: " + v);
+
+ getSetInput("Mickey Mouse");
+ triggerEvent("input", "change");
+
+ assertEquals("Mickey", m.getFirstPerson().getFirstName(), "First name updated");
+ assertEquals("Mouse", m.getFirstPerson().getLastName(), "Last name updated");
+ } catch (Throwable t) {
+ throw t;
+ } finally {
+ Utils.exposeHTML(KnockoutTest.class, "");
+ }
+ }
@KOTest public void modifyValueAssertChangeInModelOnBoolean() throws Throwable {
Object exp = Utils.exposeHTML(KnockoutTest.class,
diff -r 6db378e9aa7c json-tck/src/main/java/net/java/html/json/tests/PersonImpl.java
--- a/json-tck/src/main/java/net/java/html/json/tests/PersonImpl.java Thu Jul 02 08:27:47 2015 +0200
+++ b/json-tck/src/main/java/net/java/html/json/tests/PersonImpl.java Fri Jul 03 08:34:23 2015 +0200
@@ -58,10 +58,16 @@
@Property(name = "address", type = Address.class)
})
final class PersonImpl {
- @ComputedProperty
+ @ComputedProperty(write = "parseNames")
public static String fullName(String firstName, String lastName) {
return firstName + " " + lastName;
}
+
+ static void parseNames(Person p, String fullName) {
+ String[] arr = fullName.split(" ");
+ p.setFirstName(arr[0]);
+ p.setLastName(arr[1]);
+ }
@ComputedProperty
public static String sexType(Sex sex) {
diff -r 6db378e9aa7c json/src/main/java/net/java/html/json/ComputedProperty.java
--- a/json/src/main/java/net/java/html/json/ComputedProperty.java Thu Jul 02 08:27:47 2015 +0200
+++ b/json/src/main/java/net/java/html/json/ComputedProperty.java Fri Jul 03 08:34:23 2015 +0200
@@ -75,4 +75,31 @@
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface ComputedProperty {
+ /** Name of a method to handle changes to the computed property.
+ * By default the computed properties are read-only, however one can
+ * make them mutable by defining a static method that takes
+ * two parameters:
+ *
+ *
the model class
+ *
the value - either exactly the return the method annotated
+ * by this property or a superclass (like {@link Object})
+ *
+ * Sample code snippet using the write feature of {@link ComputedProperty}
+ * could look like this (assuming the {@link Model model class} named
+ * DataModel has int property value):
+ *
+ * {@link ComputedProperty @ComputedProperty}(write="setPowerValue")
+ * static int powerValue(int value) {
+ * return value * value;
+ * }
+ * static void setPowerValue(DataModel m, int value) {
+ * m.setValue((int){@link Math}.sqrt(value));
+ * }
+ *
+ *
+ * @return the name of a method to handle changes to the computed
+ * property
+ * @since 1.2
+ */
+ public String write() default "";
}
diff -r 6db378e9aa7c json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java
--- a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Thu Jul 02 08:27:47 2015 +0200
+++ b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Fri Jul 03 08:34:23 2015 +0200
@@ -190,7 +190,7 @@
Map> functionDeps = new HashMap>();
Prprt[] props = createProps(e, m.properties());
- if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
+ if (!generateComputedProperties(className, body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
ok = false;
}
if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
@@ -626,6 +626,7 @@
}
private boolean generateComputedProperties(
+ String className,
Writer w, Prprt[] fixedProps,
Collection extends Element> arr, Collection props,
Map> deps
@@ -646,6 +647,11 @@
continue;
}
ExecutableElement ee = (ExecutableElement)e;
+ ExecutableElement write = null;
+ if (!cp.write().isEmpty()) {
+ write = findWrite(ee, (TypeElement)e.getEnclosingElement(), cp.write(), className);
+ ok = write != null;
+ }
final TypeMirror rt = ee.getReturnType();
final Types tu = processingEnv.getTypeUtils();
TypeMirror ert = tu.erasure(rt);
@@ -743,13 +749,28 @@
w.write(" }\n");
w.write(" }\n");
- props.add(new GetSet(
- e.getSimpleName().toString(),
- gs[0],
- null,
- tn,
- true
- ));
+ if (write == null) {
+ props.add(new GetSet(
+ e.getSimpleName().toString(),
+ gs[0],
+ null,
+ tn,
+ true
+ ));
+ } else {
+ w.write(" public void " + gs[4] + "(" + write.getParameters().get(1).asType());
+ w.write(" value) {\n");
+ w.write(" " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + write.getSimpleName() + "(this, value);\n");
+ w.write(" }\n");
+
+ props.add(new GetSet(
+ e.getSimpleName().toString(),
+ gs[0],
+ gs[4],
+ tn,
+ false
+ ));
+ }
}
return ok;
@@ -767,14 +788,16 @@
pref + n,
null,
"a" + n,
- null
+ null,
+ "set" + n
};
}
return new String[]{
pref + n,
"set" + n,
"a" + n,
- ""
+ "",
+ "set" + n
};
}
@@ -2009,4 +2032,55 @@
return false;
}
+ private ExecutableElement findWrite(ExecutableElement computedPropElem, TypeElement te, String name, String className) {
+ String err = null;
+ METHODS:
+ for (Element e : te.getEnclosedElements()) {
+ if (e.getKind() != ElementKind.METHOD) {
+ continue;
+ }
+ if (!e.getSimpleName().contentEquals(name)) {
+ continue;
+ }
+ if (e.equals(computedPropElem)) {
+ continue;
+ }
+ if (!e.getModifiers().contains(Modifier.STATIC)) {
+ computedPropElem = (ExecutableElement) e;
+ err = "Would have to be static";
+ continue;
+ }
+ ExecutableElement ee = (ExecutableElement) e;
+ TypeMirror retType = computedPropElem.getReturnType();
+ final List extends VariableElement> params = ee.getParameters();
+ boolean error = false;
+ if (params.size() != 2) {
+ error = true;
+ } else {
+ String firstType = params.get(0).asType().toString();
+ int lastDot = firstType.lastIndexOf('.');
+ if (lastDot != -1) {
+ firstType = firstType.substring(lastDot + 1);
+ }
+ if (!firstType.equals(className)) {
+ error = true;
+ }
+ if (!processingEnv.getTypeUtils().isAssignable(retType, params.get(1).asType())) {
+ error = true;
+ }
+ }
+ if (error) {
+ computedPropElem = (ExecutableElement) e;
+ err = "Write method first argument needs to be " + className + " and second " + retType + " or Object";
+ continue;
+ }
+ return ee;
+ }
+ if (err == null) {
+ err = "Cannot find " + name + "(" + className + ", value) method in this class";
+ }
+ error(err, computedPropElem);
+ return null;
+ }
+
}
diff -r 6db378e9aa7c json/src/test/java/net/java/html/json/MapModelTest.java
--- a/json/src/test/java/net/java/html/json/MapModelTest.java Thu Jul 02 08:27:47 2015 +0200
+++ b/json/src/test/java/net/java/html/json/MapModelTest.java Fri Jul 03 08:34:23 2015 +0200
@@ -198,6 +198,24 @@
assertEquals(p.getSex(), Sex.FEMALE, "Changed");
}
+
+ @Test public void changeComputedProperty() {
+ Modelik p = Models.bind(new Modelik(), c);
+ p.setValue(5);
+
+ Map m = (Map)Models.toRaw(p);
+ Object o = m.get("powerValue");
+ assertNotNull(o, "Value is there");
+ assertEquals(o.getClass(), One.class);
+
+ One one = (One)o;
+ assertNotNull(one.pb, "Prop binding specified");
+
+ assertEquals(one.pb.getValue(), 25, "Power of 5");
+
+ one.pb.setValue(16);
+ assertEquals(p.getValue(), 4, "Square root of 16");
+ }
@Test public void removeViaIterator() {
People p = Models.bind(new People(), c);
diff -r 6db378e9aa7c json/src/test/java/net/java/html/json/ModelProcessorTest.java
--- a/json/src/test/java/net/java/html/json/ModelProcessorTest.java Thu Jul 02 08:27:47 2015 +0200
+++ b/json/src/test/java/net/java/html/json/ModelProcessorTest.java Fri Jul 03 08:34:23 2015 +0200
@@ -144,6 +144,72 @@
}
}
+ @Test public void writeableComputedPropertyMissingWrite() throws IOException {
+ String html = ""
+ + "";
+ String code = "package x.y.z;\n"
+ + "import net.java.html.json.Model;\n"
+ + "import net.java.html.json.Property;\n"
+ + "import net.java.html.json.ComputedProperty;\n"
+ + "@Model(className=\"XModel\", properties={\n"
+ + " @Property(name=\"prop\", type=int.class)\n"
+ + "})\n"
+ + "class X {\n"
+ + " static @ComputedProperty(write=\"setY\") int y(int prop) {\n"
+ + " return prop;\n"
+ + " }\n"
+ + "}\n";
+
+ Compile c = Compile.create(html, code);
+ assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+ boolean ok = false;
+ StringBuilder msgs = new StringBuilder();
+ for (Diagnostic extends JavaFileObject> e : c.getErrors()) {
+ String msg = e.getMessage(Locale.ENGLISH);
+ if (msg.contains("Cannot find setY")) {
+ ok = true;
+ }
+ msgs.append("\n").append(msg);
+ }
+ if (!ok) {
+ fail("Should contain warning about non-static method:" + msgs);
+ }
+ }
+
+ @Test public void writeableComputedPropertyWrongWriteType() throws IOException {
+ String html = ""
+ + "";
+ String code = "package x.y.z;\n"
+ + "import net.java.html.json.Model;\n"
+ + "import net.java.html.json.Property;\n"
+ + "import net.java.html.json.ComputedProperty;\n"
+ + "@Model(className=\"XModel\", properties={\n"
+ + " @Property(name=\"prop\", type=int.class)\n"
+ + "})\n"
+ + "class X {\n"
+ + " static @ComputedProperty(write=\"setY\") int y(int prop) {\n"
+ + " return prop;\n"
+ + " }\n"
+ + " static void setY(String prop) {\n"
+ + " }\n"
+ + "}\n";
+
+ Compile c = Compile.create(html, code);
+ assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+ boolean ok = false;
+ StringBuilder msgs = new StringBuilder();
+ for (Diagnostic extends JavaFileObject> e : c.getErrors()) {
+ String msg = e.getMessage(Locale.ENGLISH);
+ if (msg.contains("Write method first argument needs to be XModel and second int or Object")) {
+ ok = true;
+ }
+ msgs.append("\n").append(msg);
+ }
+ if (!ok) {
+ fail("Should contain warning about non-static method:" + msgs);
+ }
+ }
+
@Test public void computedCantReturnVoid() throws IOException {
String html = ""
+ "";
diff -r 6db378e9aa7c json/src/test/java/net/java/html/json/ModelTest.java
--- a/json/src/test/java/net/java/html/json/ModelTest.java Thu Jul 02 08:27:47 2015 +0200
+++ b/json/src/test/java/net/java/html/json/ModelTest.java Fri Jul 03 08:34:23 2015 +0200
@@ -173,6 +173,17 @@
assertTrue(my.mutated.contains("count"), "Count is in there: " + my.mutated);
}
+ @Test public void derivedArrayPropChange() {
+ model.applyBindings();
+ model.setCount(5);
+
+ List arr = model.getRepeat();
+ assertEquals(arr.size(), 5, "Five items: " + arr);
+
+ model.setRepeat(10);
+ assertEquals(model.getCount(), 10, "Changing repeat changes count");
+ }
+
@Test public void derivedPropertiesAreNotified() {
model.applyBindings();
@@ -255,11 +266,15 @@
static void doSomething() {
}
- @ComputedProperty
+ @ComputedProperty(write = "setPowerValue")
static int powerValue(int value) {
return value * value;
}
+ static void setPowerValue(Modelik m, int value) {
+ m.setValue((int)Math.sqrt(value));
+ }
+
@OnPropertyChange({ "powerValue", "unrelated" })
static void aPropertyChanged(Modelik m, String name) {
m.setChangedProperty(name);
@@ -278,6 +293,13 @@
model.setValue(33);
assertEquals(model.getChangedProperty(), "powerValue", "power property changed");
}
+ @Test public void changePowerValue() {
+ model.setValue(3);
+ assertEquals(model.getPowerValue(), 9, "Square");
+ model.setPowerValue(16);
+ assertEquals(model.getValue(), 4, "Square root");
+ assertEquals(model.getPowerValue(), 16, "Square changed");
+ }
@Test public void changeUnrelated() {
model.setUnrelated(333);
assertEquals(model.getChangedProperty(), "unrelated", "unrelated changed");
@@ -302,10 +324,13 @@
return "Not allowed callback!";
}
- @ComputedProperty
+ @ComputedProperty(write="parseRepeat")
static List repeat(int count) {
return Collections.nCopies(count, "Hello");
}
+ static void parseRepeat(Modelik m, Object v) {
+ m.setCount((Integer)v);
+ }
public @Test void hasPersonPropertyAndComputedFullName() {
List arr = model.getPeople();
diff -r 6db378e9aa7c src/main/javadoc/overview.html
--- a/src/main/javadoc/overview.html Thu Jul 02 08:27:47 2015 +0200
+++ b/src/main/javadoc/overview.html Fri Jul 03 08:34:23 2015 +0200
@@ -79,7 +79,8 @@
One can control {@link net.java.html.json.OnReceive#headers() HTTP request headers}
when connecting to server using the {@link net.java.html.json.OnReceive}
- annotation.
+ annotation. It is possible to have
+ {@link net.java.html.json.ComputedProperty#write() writable computed properties}.
Bugfix of issues 250503,
252987.