diff -r 5c7a73f64724 api.knockout/nbproject/project.xml --- a/api.knockout/nbproject/project.xml Tue Mar 10 05:38:55 2015 +0100 +++ b/api.knockout/nbproject/project.xml Wed Mar 18 16:18:33 2015 +0100 @@ -41,6 +41,14 @@ + org.openide.util + + + + 9.3 + + + org.openide.util.lookup diff -r 5c7a73f64724 api.knockout/src/org/netbeans/spi/knockout/Bindings.java --- a/api.knockout/src/org/netbeans/spi/knockout/Bindings.java Tue Mar 10 05:38:55 2015 +0100 +++ b/api.knockout/src/org/netbeans/spi/knockout/Bindings.java Wed Mar 18 16:18:33 2015 +0100 @@ -42,13 +42,19 @@ package org.netbeans.spi.knockout; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.netbeans.spi.knockout.BindingsProvider.Response; import org.openide.filesystems.FileObject; +import org.openide.util.BaseUtilities; import org.openide.util.Lookup; +import org.openide.util.TopologicalSortException; /** * Allows structural description of a ko.applyBindings parameter. @@ -63,7 +69,7 @@ private final String name; private final List subBindings = new ArrayList<>(); - private final List props = new ArrayList<>(); + private final List props = new ArrayList<>(); private Bindings(String name) { this.name = name; @@ -144,7 +150,7 @@ */ public final Bindings modelProperty(String name, Bindings binding, boolean array) { subBindings.add(binding); - addProp(name, array, binding.name); + addProp(name, array, binding); return this; } @@ -152,44 +158,78 @@ StringBuilder sb = new StringBuilder(); //sb.append("(function() {\n"); HashSet visited = new HashSet<>(); - LinkedHashSet lhs = new LinkedHashSet<>(); - walkBindings(visited, lhs); + Collection lhs = new LinkedHashSet<>(); + StringBuilder delayedInit = new StringBuilder(); + if (!walkBindings(visited, lhs)) { + Map> edges = new HashMap<>(); + for (Bindings b : lhs) { + edges.put(b, b.subBindings); + } + try { + BaseUtilities.topologicalSort(lhs, edges); + // There is a cycle, so the resolution should fail + throw new IllegalStateException(); + } catch (TopologicalSortException ex) { + lhs = Collections.checkedList(ex.partialSort(), Bindings.class); + } + } for (Bindings b : lhs) { - b.generate(sb); + b.generate(sb, delayedInit, visited); + visited.remove(b); } - sb.append(" ko.applyBindings(").append(name).append(");\n"); + sb.append(delayedInit); + sb.append("\nko.applyBindings(").append(name).append(");\n"); //sb.append("}());"); return sb.toString(); } - private void generate(StringBuilder sb) { + private void generate(StringBuilder sb, StringBuilder delayedInit, Set notYetProcessed) { sb.append("var ").append(name).append(" = {"); String sep = "\n"; - for (String s : props) { - sb.append(sep).append(" ").append(s); + for (int i = 0; i < props.size(); i += 3) { + String propName = (String)props.get(i); + Boolean array = (Boolean)props.get(i + 1); + Object value = props.get(i + 2); + + if (value instanceof Bindings) { + Bindings b = (Bindings) value; + if (notYetProcessed.contains(b)) { + delayedInit.append("\n").append(this.name).append("[\""). + append(propName).append("\"] = ").append(b.name). + append(";"); + continue; + } + value = b.name; + } + + if (array) { + value = "[ " + value + " ]"; + } + sb.append(sep).append(" ").append('\"').append(propName).append("\" : ").append(value); sep = ",\n"; } sb.append("\n};\n"); } - private void addProp(String name, boolean array, String value) { - if (array) { - value = "[ " + value + " ]"; - } + private void addProp(String name, boolean array, Object value) { if (name.contains("\"")) { throw new IllegalStateException("Wrong name " + name); } - props.add('\"' + name + "\" : " + value); + props.add(name); + props.add(array); + props.add(value); } - private void walkBindings(Set visited, Set collect) { + private boolean walkBindings(Set visited, Collection collect) { if (!visited.add(this)) { - return; + return false; } + boolean ok = true; for (Bindings b : subBindings) { - b.walkBindings(visited, collect); + ok &= b.walkBindings(visited, collect); } collect.add(this); + return ok; } /** diff -r 5c7a73f64724 api.knockout/test/unit/src/org/netbeans/spi/knockout/BindingsNGTest.java --- a/api.knockout/test/unit/src/org/netbeans/spi/knockout/BindingsNGTest.java Tue Mar 10 05:38:55 2015 +0100 +++ b/api.knockout/test/unit/src/org/netbeans/spi/knockout/BindingsNGTest.java Wed Mar 18 16:18:33 2015 +0100 @@ -91,6 +91,36 @@ assertEquals(eng.eval("ko.value.currentTweets[0].from_user_id"), 0d, "Boolean values are set to true"); } + @Test + public void generateRecursiveModel() throws Exception { + Bindings m1 = Bindings.create("Hello"); + Bindings m2 = Bindings.create("Multi"); + m1.modelProperty("multi", m2, false); + m2.modelProperty("hello", m1, false); + m2.intProperty("int", false); + String txt = m2.generate(); + assertValidJS(txt); + assertNotNull(eng.eval("ko"), txt); + assertNotNull(eng.eval("Hello.multi"), txt); + assertEquals(eng.eval("Hello.multi.hello === Hello"), Boolean.TRUE, txt); + } + + @Test + public void generateRecursiveModel2() throws Exception { + Bindings one = Bindings.create("One"); + Bindings two = Bindings.create("Two"); + Bindings three = Bindings.create("Three"); + one.modelProperty("three", three, false); + two.modelProperty("one", one, false); + three.modelProperty("two",two , false); + String txt = two.generate(); + assertValidJS(txt); + assertNotNull(eng.eval("ko"), txt); + assertNotNull(eng.eval("One.three"), txt); + assertNotNull(eng.eval("One.three.two"), txt); + assertEquals(eng.eval("One.three.two.one === One"), Boolean.TRUE, txt); + } + private void assertValidJS(String txt) { assertNotNull(txt, "We have some script"); try {