diff -r d06b9f7976d1 json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java --- a/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -71,7 +71,7 @@ try { GC m = Models.bind(new GC(), ctx); m.getAll().add(new Fullname("Jarda", "Tulach")); - m.applyBindings(); + Models.applyBindings(m); int cnt = Utils.countChildren(GCKnockoutTest.class, "ul"); assert cnt == 1 : "One child, but was " + cnt; diff -r d06b9f7976d1 json-tck/src/main/java/net/java/html/json/tests/JSONTest.java --- a/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -57,7 +57,7 @@ * * @author Jaroslav Tulach */ -@Model(className = "JSONik", properties = { +@Model(className = "JSONik", targetId = "", properties = { @Property(name = "fetched", type = Person.class), @Property(name = "fetchedCount", type = int.class), @Property(name = "fetchedResponse", type = String.class), diff -r d06b9f7976d1 json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java --- a/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -58,7 +58,7 @@ * * @author Jaroslav Tulach */ -@Model(className="KnockoutModel", properties={ +@Model(className="KnockoutModel", targetId = "", properties={ @Property(name="name", type=String.class), @Property(name="results", type=String.class, array = true), @Property(name="numbers", type=int.class, array = true), diff -r d06b9f7976d1 json-tck/src/main/java/net/java/html/json/tests/MinesTest.java --- a/json-tck/src/main/java/net/java/html/json/tests/MinesTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json-tck/src/main/java/net/java/html/json/tests/MinesTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -57,7 +57,7 @@ /** Tests model of a mine field and its behavior in the browser. */ -@Model(className = "Mines", properties = { +@Model(className = "Mines", targetId = "", properties = { @Property(name = "state", type = MinesTest.GameState.class), @Property(name = "rows", type = Row.class, array = true), }) diff -r d06b9f7976d1 json-tck/src/main/java/net/java/html/json/tests/PairModel.java --- a/json-tck/src/main/java/net/java/html/json/tests/PairModel.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json-tck/src/main/java/net/java/html/json/tests/PairModel.java Thu Dec 18 10:44:48 2014 +0100 @@ -54,7 +54,7 @@ * * @author Jaroslav Tulach */ -@Model(className = "Pair", properties = { +@Model(className = "Pair", targetId = "", properties = { @Property(name = "firstName", type = String.class), @Property(name = "lastName", type = String.class), @Property(name = "next", type = Pair.class) diff -r d06b9f7976d1 json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java --- a/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -53,7 +53,7 @@ * * @author Jaroslav Tulach */ -@Model(className = "WebSocketik", properties = { +@Model(className = "WebSocketik", targetId="", properties = { @Property(name = "fetched", type = Person.class), @Property(name = "fetchedCount", type = int.class), @Property(name = "open", type = int.class), diff -r d06b9f7976d1 json/src/main/java/net/java/html/json/Model.java --- a/json/src/main/java/net/java/html/json/Model.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/main/java/net/java/html/json/Model.java Thu Dec 18 10:44:48 2014 +0100 @@ -48,6 +48,7 @@ import java.lang.annotation.Target; import java.net.URL; import java.util.List; +import org.netbeans.html.json.spi.Technology; /** Defines a model class that represents a single * JSON-like object @@ -122,7 +123,7 @@ *

* In case you are using Knockout technology * for Java then you can associate such model object with surrounding HTML page by - * calling: p.applyBindings();. The page can then use regular + * calling: p.applyBindings(); (in case you specify {@link #tar. The page can then use regular * Knockout bindings to reference your * model and create dynamic connection between your model in Java and * live DOM structure in the browser: @@ -202,4 +203,22 @@ * @return array of property definitions */ Property[] properties(); + + /** The id of an element to bind this model too. If this + * property is specified an applyBindings method + * in the model class is going to be generated which later calls + * {@link Models#applyBindings(java.lang.Object, java.lang.String)} + * with appropriate targetId. If the targetId + * is specified as empty string, null value is passed + * to {@link Models#applyBindings(java.lang.Object, java.lang.String)} method. + * If the targetId is not specified at all, no public + * applyBindings method is generated at all (a change compared + * to previous versions of this API). + * + * @return an empty string (means apply globally), or ID of a (usually DOM) + * element to apply this model after calling its generated + * applyBindings() method to + * @since 1.1 + */ + String targetId() default ""; } diff -r d06b9f7976d1 json/src/main/java/net/java/html/json/Models.java --- a/json/src/main/java/net/java/html/json/Models.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/main/java/net/java/html/json/Models.java Thu Dec 18 10:44:48 2014 +0100 @@ -164,6 +164,25 @@ * @since 0.7 */ public static void applyBindings(Object model) { - JSON.applyBindings(model); + JSON.applyBindings(model, null); + } + + + /** Apply bindings of a model class. Each model class has an + * apply bindings method that "activates" it. In ko4j mode, + * it binds the model values to the currently active page. One + * can call the generated method directly, or use this static + * helper method to perform the activation. + * + * @param model instance of a {@link Model class} + * @param targetId the id of the element to apply the binding to + * @throws IllegalArgumentException if the model is not + * instance of a class generated by {@link Model model annotation} + * processor. + * + * @since 1.1 + */ + public static void applyBindings(Object model, String targetId) { + JSON.applyBindings(model, targetId); } } diff -r d06b9f7976d1 json/src/main/java/org/netbeans/html/json/impl/Bindings.java --- a/json/src/main/java/org/netbeans/html/json/impl/Bindings.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java Thu Dec 18 10:44:48 2014 +0100 @@ -42,6 +42,8 @@ */ package org.netbeans.html.json.impl; +import java.util.logging.Level; +import java.util.logging.Logger; import net.java.html.BrwsrCtx; import org.netbeans.html.json.spi.FunctionBinding; import org.netbeans.html.json.spi.PropertyBinding; @@ -53,6 +55,8 @@ * @author Jaroslav Tulach */ public final class Bindings { + private static final Logger LOG = Logger.getLogger(Bindings.class.getName()); + private Data data; private final Technology bp; @@ -105,7 +109,18 @@ } } - public void applyBindings() { + public void applyBindings(String id) { + if (bp instanceof Technology.ApplyId) { + Technology.ApplyId ai = (Technology.ApplyId) bp; + ai.applyBindings(id, data); + return; + } + if (id != null) { + LOG.log(Level.WARNING, + "Technology {0} does not implement ApplyId extension. Can't apply to {1}. Applying globally.", + new Object[]{bp, id} + ); + } bp.applyBindings(data); } diff -r d06b9f7976d1 json/src/main/java/org/netbeans/html/json/impl/JSON.java --- a/json/src/main/java/org/netbeans/html/json/impl/JSON.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/main/java/org/netbeans/html/json/impl/JSON.java Thu Dec 18 10:44:48 2014 +0100 @@ -296,12 +296,12 @@ return find(object, null); } - public static void applyBindings(Object object) { + public static void applyBindings(Object object, String id) { final Proto proto = findProto(object); if (proto == null) { throw new IllegalArgumentException("Not a model: " + object.getClass()); } - proto.applyBindings(); + proto.applyBindings(id); } public static void loadJSON( diff -r d06b9f7976d1 json/src/main/java/org/netbeans/html/json/impl/JSONList.java --- a/json/src/main/java/org/netbeans/html/json/impl/JSONList.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/main/java/org/netbeans/html/json/impl/JSONList.java Thu Dec 18 10:44:48 2014 +0100 @@ -179,7 +179,7 @@ public void run() { Bindings m = PropertyBindingAccessor.getBindings(proto, false); if (m != null) { - m.valueHasMutated(name, null, null); + m.valueHasMutated(name, null, JSONList.this); for (String dependant : deps) { m.valueHasMutated(dependant, null, null); } diff -r d06b9f7976d1 json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java --- a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Thu Dec 18 10:44:48 2014 +0100 @@ -499,17 +499,31 @@ w.append(" }\n"); writeToString(props, w); writeClone(className, props, w); - w.write(" /** Activates this model instance in the current {@link \n" - + "net.java.html.json.Models#bind(java.lang.Object, net.java.html.BrwsrCtx) browser context}. \n" - + "In case of using Knockout technology, this means to \n" - + "bind JSON like data in this model instance with Knockout tags in \n" - + "the surrounding HTML page.\n" - + "*/\n" - ); - w.write(" public " + className + " applyBindings() {\n"); - w.write(" proto.applyBindings();\n"); - w.write(" return this;\n"); - w.write(" }\n"); + String targetId = findTargetId(e); + if (targetId != null) { + w.write(" /** Activates this model instance in the current {@link \n" + + "net.java.html.json.Models#bind(java.lang.Object, net.java.html.BrwsrCtx) browser context}. \n" + + "In case of using Knockout technology, this means to \n" + + "bind JSON like data in this model instance with Knockout tags in \n" + + "the surrounding HTML page.\n" + ); + if (targetId != null) { + w.write("This method binds to element '" + targetId + "' on the page\n"); + } + w.write("" + + "@return this object\n" + + "*/\n" + ); + w.write(" public " + className + " applyBindings() {\n"); + w.write(" proto.applyBindings();\n"); + // w.write(" proto.applyBindings(id);\n"); + w.write(" return this;\n"); + w.write(" }\n"); + } else { + w.write(" private " + className + " applyBindings() {\n"); + w.write(" throw new IllegalStateException(\"Please specify targetId=\\\"\\\" in your @Model annotation\");\n"); + w.write(" }\n"); + } w.write(" public boolean equals(Object o) {\n"); w.write(" if (o == this) return true;\n"); w.write(" if (!(o instanceof " + className + ")) return false;\n"); @@ -1757,6 +1771,21 @@ } } + private static String findTargetId(Element e) { + for (AnnotationMirror m : e.getAnnotationMirrors()) { + if (m.getAnnotationType().toString().equals(Model.class.getName())) { + for (Map.Entry entrySet : m.getElementValues().entrySet()) { + ExecutableElement key = entrySet.getKey(); + AnnotationValue value = entrySet.getValue(); + if ("targetId()".equals(key.toString())) { + return value.toString(); + } + } + } + } + return null; + } + private static class Prprt { private final Element e; private final AnnotationMirror tm; diff -r d06b9f7976d1 json/src/main/java/org/netbeans/html/json/spi/Proto.java --- a/json/src/main/java/org/netbeans/html/json/spi/Proto.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java Thu Dec 18 10:44:48 2014 +0100 @@ -193,7 +193,21 @@ * of the current model to the body element of the page. */ public void applyBindings() { - initBindings().applyBindings(); + initBindings().applyBindings(null); + } + + /** Initializes the associated model to the specified element's subtree. + * The technology is taken from the current {@link #getContext() context} and + * in case of knockout.js applies given bindings + * of the current model to the element of the page with 'id' attribute + * set to the specified id value. + * + * @param id the id of element to apply the binding to + * @since 1.1 + * @see Technology.ApplyId + */ + public void applyBindings(String id) { + initBindings().applyBindings(id); } /** Invokes the provided runnable in the {@link #getContext() context} diff -r d06b9f7976d1 json/src/main/java/org/netbeans/html/json/spi/Technology.java --- a/json/src/main/java/org/netbeans/html/json/spi/Technology.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/main/java/org/netbeans/html/json/spi/Technology.java Thu Dec 18 10:44:48 2014 +0100 @@ -173,4 +173,22 @@ */ public void valueHasMutated(D data, String propertyName, Object oldValue, Object newValue); } + + /** Apply technology bindings at selected subtree of the HTML page. + * Can be accessed via {@link Proto#applyBindings(java.lang.String)} or + * via method applyBindings(String) generated when one + * is using the {@link Model} annotation. + * + * @param the internal data for the technology + * @since 1.1 + */ + public static interface ApplyId extends Technology { + /** Applies given data to current context (usually an element on an + * HTML page). + * + * @param id the id of an element to apply the data to + * @param data the data to apply + */ + public void applyBindings(String id, D data); + } } diff -r d06b9f7976d1 json/src/test/java/net/java/html/json/MapModelTest.java --- a/json/src/test/java/net/java/html/json/MapModelTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/test/java/net/java/html/json/MapModelTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -74,8 +74,20 @@ register(Transfer.class, t, 1).build(); } + @Test public void isThereNoApplyBinding() throws Exception { + try { + Person.class.getMethod("applyBindings"); + } catch (NoSuchMethodException ex) { + // OK + return; + } + fail("There should be no applyBindings() method"); + } + @Test public void isThereABinding() throws Exception { - Person p = Models.bind(new Person(), c).applyBindings(); + Person p = Models.bind(new Person(), c); + Models.applyBindings(p); + assertNull(t.appliedId, "Applied globally"); p.setFirstName("Jarda"); Map m = (Map)Models.toRaw(p); @@ -94,6 +106,11 @@ assertEquals(o.changes, 2, "Snd change"); } + @Test public void applyLocally() throws Exception { + Person p = Models.bind(new Person(), c); + Models.applyBindings(p, "local"); + assertEquals(t.appliedId, "local", "Applied locally"); + } @Test public void dontNotifySameProperty() throws Exception { Person p = Models.bind(new Person(), c); @@ -293,7 +310,9 @@ } static final class MapTechnology - implements Technology>, Transfer { + implements Technology.ApplyId>, Transfer { + private Map appliedData; + private String appliedId; @Override public Map wrapModel(Object model) { @@ -329,6 +348,7 @@ @Override public void applyBindings(Map data) { + throw new UnsupportedOperationException("Never called!"); } @Override @@ -370,5 +390,11 @@ public void runSafe(Runnable r) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } + + @Override + public void applyBindings(String id, Map data) { + this.appliedId = id; + this.appliedData = data; + } } } diff -r d06b9f7976d1 json/src/test/java/net/java/html/json/ModelTest.java --- a/json/src/test/java/net/java/html/json/ModelTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/test/java/net/java/html/json/ModelTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -60,7 +60,7 @@ * * @author Jaroslav Tulach */ -@Model(className = "Modelik", properties = { +@Model(className = "Modelik", targetId = "", properties = { @Property(name = "value", type = int.class), @Property(name = "count", type = int.class), @Property(name = "unrelated", type = long.class), diff -r d06b9f7976d1 json/src/test/java/net/java/html/json/PersonImpl.java --- a/json/src/test/java/net/java/html/json/PersonImpl.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/test/java/net/java/html/json/PersonImpl.java Thu Dec 18 10:44:48 2014 +0100 @@ -92,7 +92,7 @@ } } - @Model(className = "People", properties = { + @Model(className = "People", targetId="myPeople", properties = { @Property(array = true, name = "info", type = Person.class), @Property(array = true, name = "nicknames", type = String.class), @Property(array = true, name = "age", type = int.class), diff -r d06b9f7976d1 json/src/test/java/org/netbeans/html/json/impl/DeepChangeTest.java --- a/json/src/test/java/org/netbeans/html/json/impl/DeepChangeTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/test/java/org/netbeans/html/json/impl/DeepChangeTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -77,7 +77,7 @@ register(Transfer.class, t, 1).build(); } - @Model(className = "MyX", properties = { + @Model(className = "MyX", targetId = "anythingX", properties = { @Property(name = "one", type = MyY.class), @Property(name = "all", type = MyY.class, array = true) }) @@ -253,7 +253,8 @@ @Test public void firstChangeInArrayNotifiedTransitively() throws Exception { MyOverall p = Models.bind( new MyOverall(new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)) - ), c).applyBindings(); + ), c); + Models.applyBindings(p); Map m = (Map)Models.toRaw(p); Object v = m.get("valueAccross"); diff -r d06b9f7976d1 json/src/test/java/org/netbeans/html/json/impl/EmployerTest.java --- a/json/src/test/java/org/netbeans/html/json/impl/EmployerTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/test/java/org/netbeans/html/json/impl/EmployerTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -60,6 +60,6 @@ @Test public void preLoadsTheClass() { Employer em = Models.fromRaw(BrwsrCtx.EMPTY, Employer.class, this); Assert.assertNotNull(em, "Class loaded"); - em.applyBindings(); + Models.applyBindings(em); } } diff -r d06b9f7976d1 json/src/test/java/org/netbeans/html/json/impl/ToDoTest.java --- a/json/src/test/java/org/netbeans/html/json/impl/ToDoTest.java Thu Dec 18 09:22:47 2014 +0100 +++ b/json/src/test/java/org/netbeans/html/json/impl/ToDoTest.java Thu Dec 18 10:44:48 2014 +0100 @@ -105,7 +105,8 @@ new Todo("First", false), new Todo("2nd", true), new Todo("Third", false) - ), c).applyBindings(); + ), c); + Models.applyBindings(ui); Map m = (Map) Models.toRaw(ui); Object v = m.get("remaining"); diff -r d06b9f7976d1 ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java Thu Dec 18 09:22:47 2014 +0100 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java Thu Dec 18 10:44:48 2014 +0100 @@ -57,7 +57,7 @@ */ @Contexts.Id("ko4j") final class KOTech -implements Technology.BatchInit, Technology.ValueMutated { +implements Technology.BatchInit, Technology.ValueMutated, Technology.ApplyId { private Object[] jsObjects; private int jsIndex; @@ -131,7 +131,11 @@ @Override public void applyBindings(Object data) { - Object ko = Knockout.applyBindings(data); + applyBindings(null, data); + } + @Override + public void applyBindings(String id, Object data) { + Object ko = Knockout.applyBindings(id, data); if (ko instanceof Knockout) { ((Knockout)ko).hold(); } diff -r d06b9f7976d1 ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Thu Dec 18 09:22:47 2014 +0100 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Thu Dec 18 10:44:48 2014 +0100 @@ -131,12 +131,14 @@ Object model, String prop, Object oldValue, Object newValue ); - @JavaScriptBody(args = { "bindings" }, body = - "ko['cleanNode'](window['document']['body']);\n" + - "ko['applyBindings'](bindings);\n" + + @JavaScriptBody(args = { "id", "bindings" }, body = + "var d = window['document'];\n" + + "var e = id ? d['getElementById'](id) : d['body'];\n" + + "ko['cleanNode'](e);\n" + + "ko['applyBindings'](bindings, e);\n" + "return bindings['ko4j'];\n" ) - native static Object applyBindings(Object bindings); + native static Object applyBindings(String id, Object bindings); @JavaScriptBody(args = { "cnt" }, body = "var arr = new Array(cnt);\n" + diff -r d06b9f7976d1 ko4j/src/test/java/org/netbeans/html/ko4j/LessCallbacksCheck.java --- a/ko4j/src/test/java/org/netbeans/html/ko4j/LessCallbacksCheck.java Thu Dec 18 09:22:47 2014 +0100 +++ b/ko4j/src/test/java/org/netbeans/html/ko4j/LessCallbacksCheck.java Thu Dec 18 10:44:48 2014 +0100 @@ -53,7 +53,7 @@ * * @author Jaroslav Tulach */ -@Model(className = "LessCalls", properties = { +@Model(className = "LessCalls", targetId = "", properties = { @Property(name = "value", type = int.class) }) public class LessCallbacksCheck { diff -r d06b9f7976d1 src/main/javadoc/overview.html --- a/src/main/javadoc/overview.html Thu Dec 18 09:22:47 2014 +0100 +++ b/src/main/javadoc/overview.html Thu Dec 18 10:44:48 2014 +0100 @@ -94,6 +94,20 @@ {@link org.netbeans.html.json.spi.Transfer Java based JSON} and {@link org.netbeans.html.json.spi.WSTransfer WebSocket} implementations under the name tyrus. +

+

+ A particular DOM subtree + that a knockout.js model gets + applied to can be selected by using + {@link net.java.html.json.Models#applyBindings(java.lang.Object,%20java.lang.String) + Models.applyBindings(m, id)} with an id of an HTML element. + There is new Model.targetId() attribute. It it is used the generated + applyBindings method will bind to specified target id. + If the attribute is not specified, the applyBindings + is not generated. One can still use Models.applyBindings + to bind such model, however. +

+

Memory model when using Knockout bindings has been improved (required additions of two new methods: {@link org.netbeans.html.json.spi.PropertyBinding#weak()} and