1.1 --- a/benchmarks/matrix-multiplication/src/test/java/org/apidesign/benchmark/matrixmul/MatrixTest.java Tue Jan 22 19:21:34 2013 +0100
1.2 +++ b/benchmarks/matrix-multiplication/src/test/java/org/apidesign/benchmark/matrixmul/MatrixTest.java Wed Jan 23 10:57:05 2013 +0100
1.3 @@ -30,7 +30,8 @@
1.4 public MatrixTest() {
1.5 }
1.6
1.7 - @Compare public String tenThousandIterations() throws IOException {
1.8 + @Compare(scripting = false)
1.9 + public String tenThousandIterations() throws IOException {
1.10
1.11 Matrix m1 = new Matrix(5);
1.12 Matrix m2 = new Matrix(5);
2.1 --- a/javaquery/api/pom.xml Tue Jan 22 19:21:34 2013 +0100
2.2 +++ b/javaquery/api/pom.xml Wed Jan 23 10:57:05 2013 +0100
2.3 @@ -64,5 +64,11 @@
2.4 <type>jar</type>
2.5 <scope>test</scope>
2.6 </dependency>
2.7 + <dependency>
2.8 + <groupId>${project.groupId}</groupId>
2.9 + <artifactId>vmtest</artifactId>
2.10 + <version>${project.version}</version>
2.11 + <scope>test</scope>
2.12 + </dependency>
2.13 </dependencies>
2.14 </project>
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Wed Jan 23 10:57:05 2013 +0100
3.3 @@ -0,0 +1,93 @@
3.4 +/**
3.5 + * Back 2 Browser Bytecode Translator
3.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
3.7 + *
3.8 + * This program is free software: you can redistribute it and/or modify
3.9 + * it under the terms of the GNU General Public License as published by
3.10 + * the Free Software Foundation, version 2 of the License.
3.11 + *
3.12 + * This program is distributed in the hope that it will be useful,
3.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
3.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3.15 + * GNU General Public License for more details.
3.16 + *
3.17 + * You should have received a copy of the GNU General Public License
3.18 + * along with this program. Look for COPYING file in the top folder.
3.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
3.20 + */
3.21 +package org.apidesign.bck2brwsr.htmlpage;
3.22 +
3.23 +import java.lang.reflect.Method;
3.24 +import org.apidesign.bck2brwsr.core.ExtraJavaScript;
3.25 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
3.26 +
3.27 +/** Provides binding between models and
3.28 + *
3.29 + * @author Jaroslav Tulach <jtulach@netbeans.org>
3.30 + */
3.31 +@ExtraJavaScript(resource = "/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js")
3.32 +public class Knockout {
3.33 + /** used by tests */
3.34 + static Knockout next;
3.35 +
3.36 + Knockout() {
3.37 + }
3.38 +
3.39 + public static <M> Knockout applyBindings(
3.40 + Class<M> modelClass, M model, String[] propsGettersAndSetters
3.41 + ) {
3.42 + Knockout bindings = next;
3.43 + next = null;
3.44 + if (bindings == null) {
3.45 + bindings = new Knockout();
3.46 + }
3.47 + for (int i = 0; i < propsGettersAndSetters.length; i += 4) {
3.48 + try {
3.49 + Method getter = modelClass.getMethod(propsGettersAndSetters[i + 3]);
3.50 + bind(bindings, model, propsGettersAndSetters[i],
3.51 + propsGettersAndSetters[i + 1],
3.52 + propsGettersAndSetters[i + 2],
3.53 + getter.getReturnType().isPrimitive()
3.54 + );
3.55 + } catch (NoSuchMethodException ex) {
3.56 + throw new IllegalStateException(ex.getMessage());
3.57 + }
3.58 + }
3.59 + applyBindings(bindings);
3.60 + return bindings;
3.61 + }
3.62 +
3.63 + @JavaScriptBody(args = { "prop" }, body =
3.64 + "this[prop].valueHasMutated();"
3.65 + )
3.66 + public void valueHasMutated(String prop) {
3.67 + }
3.68 +
3.69 +
3.70 + @JavaScriptBody(args = { "id", "ev" }, body = "ko.utils.triggerEvent(window.document.getElementById(id), ev.substring(2));")
3.71 + public static void triggerEvent(String id, String ev) {
3.72 + }
3.73 +
3.74 + @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive" }, body =
3.75 + "var bnd = {\n"
3.76 + + " read: function() {\n"
3.77 + + " var v = model[getter]();\n"
3.78 + + " return v;\n"
3.79 + + " },\n"
3.80 + + " owner: bindings\n"
3.81 + + "};\n"
3.82 + + "if (setter != null) {\n"
3.83 + + " bnd.write = function(val) {\n"
3.84 + + " model[setter](primitive ? new Number(val) : val);\n"
3.85 + + " };\n"
3.86 + + "}\n"
3.87 + + "bindings[prop] = ko.computed(bnd);"
3.88 + )
3.89 + private static void bind(
3.90 + Object bindings, Object model, String prop, String getter, String setter, boolean primitive
3.91 + ) {
3.92 + }
3.93 +
3.94 + @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
3.95 + private static void applyBindings(Object bindings) {}
3.96 +}
4.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Tue Jan 22 19:21:34 2013 +0100
4.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Wed Jan 23 10:57:05 2013 +0100
4.3 @@ -22,9 +22,13 @@
4.4 import java.io.OutputStreamWriter;
4.5 import java.io.Writer;
4.6 import java.util.ArrayList;
4.7 +import java.util.Collection;
4.8 import java.util.Collections;
4.9 +import java.util.HashMap;
4.10 +import java.util.LinkedHashSet;
4.11 import java.util.List;
4.12 import java.util.Locale;
4.13 +import java.util.Map;
4.14 import java.util.Set;
4.15 import javax.annotation.processing.AbstractProcessor;
4.16 import javax.annotation.processing.Completion;
4.17 @@ -39,12 +43,16 @@
4.18 import javax.lang.model.element.Modifier;
4.19 import javax.lang.model.element.PackageElement;
4.20 import javax.lang.model.element.TypeElement;
4.21 +import javax.lang.model.element.VariableElement;
4.22 +import javax.lang.model.type.MirroredTypeException;
4.23 import javax.lang.model.type.TypeMirror;
4.24 import javax.tools.Diagnostic;
4.25 import javax.tools.FileObject;
4.26 import javax.tools.StandardLocation;
4.27 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
4.28 import org.apidesign.bck2brwsr.htmlpage.api.On;
4.29 import org.apidesign.bck2brwsr.htmlpage.api.Page;
4.30 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
4.31 import org.openide.util.lookup.ServiceProvider;
4.32
4.33 /** Annotation processor to process an XHTML page and generate appropriate
4.34 @@ -86,19 +94,44 @@
4.35 try {
4.36 w.append("package " + pkg + ";\n");
4.37 w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
4.38 - w.append("class ").append(className).append(" {\n");
4.39 + w.append("final class ").append(className).append(" {\n");
4.40 + w.append(" private boolean locked;\n");
4.41 + if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
4.42 + return false;
4.43 + }
4.44 for (String id : pp.ids()) {
4.45 String tag = pp.tagNameForId(id);
4.46 String type = type(tag);
4.47 - w.append(" ").append("public static final ").
4.48 + w.append(" ").append("public final ").
4.49 append(type).append(' ').append(cnstnt(id)).append(" = new ").
4.50 append(type).append("(\"").append(id).append("\");\n");
4.51 }
4.52 - w.append(" static {\n");
4.53 - if (!initializeOnClick((TypeElement) e, w, pp)) {
4.54 - return false;
4.55 + List<String> propsGetSet = new ArrayList<String>();
4.56 + Map<String,Collection<String>> propsDeps = new HashMap<String, Collection<String>>();
4.57 + generateComputedProperties(w, e.getEnclosedElements(), propsGetSet, propsDeps);
4.58 + generateProperties(w, p.properties(), propsGetSet, propsDeps);
4.59 + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
4.60 + if (!propsGetSet.isEmpty()) {
4.61 + w.write("public " + className + " applyBindings() {\n");
4.62 + w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
4.63 + w.write(className + ".class, this, ");
4.64 + w.write("new String[] {\n");
4.65 + String sep = "";
4.66 + for (String n : propsGetSet) {
4.67 + w.write(sep);
4.68 + if (n == null) {
4.69 + w.write(" null");
4.70 + } else {
4.71 + w.write(" \"" + n + "\"");
4.72 + }
4.73 + sep = ",\n";
4.74 + }
4.75 + w.write("\n });\n return this;\n}\n");
4.76 +
4.77 + w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
4.78 + w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
4.79 + w.write("}\n");
4.80 }
4.81 - w.append(" }\n");
4.82 w.append("}\n");
4.83 } finally {
4.84 w.close();
4.85 @@ -144,12 +177,17 @@
4.86 return id.toUpperCase(Locale.ENGLISH).replace('.', '_').replace('-', '_');
4.87 }
4.88
4.89 - private boolean initializeOnClick(TypeElement type, Writer w, ProcessPage pp) throws IOException {
4.90 + private boolean initializeOnClick(
4.91 + String className, TypeElement type, Writer w, ProcessPage pp
4.92 + ) throws IOException {
4.93 TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
4.94 { //for (Element clazz : pe.getEnclosedElements()) {
4.95 // if (clazz.getKind() != ElementKind.CLASS) {
4.96 // continue;
4.97 // }
4.98 + w.append(" public ").append(className).append("() {\n");
4.99 + StringBuilder dispatch = new StringBuilder();
4.100 + int dispatchCnt = 0;
4.101 for (Element method : type.getEnclosedElements()) {
4.102 On oc = method.getAnnotation(On.class);
4.103 if (oc != null) {
4.104 @@ -159,15 +197,33 @@
4.105 return false;
4.106 }
4.107 ExecutableElement ee = (ExecutableElement)method;
4.108 - boolean hasParam;
4.109 - if (ee.getParameters().isEmpty()) {
4.110 - hasParam = false;
4.111 - } else {
4.112 - if (ee.getParameters().size() != 1 || ee.getParameters().get(0).asType() != stringType) {
4.113 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method should either have no arguments or one String argument", ee);
4.114 + StringBuilder params = new StringBuilder();
4.115 + {
4.116 + boolean first = true;
4.117 + for (VariableElement ve : ee.getParameters()) {
4.118 + if (!first) {
4.119 + params.append(", ");
4.120 + }
4.121 + first = false;
4.122 + if (ve.asType() == stringType) {
4.123 + params.append('"').append(id).append('"');
4.124 + continue;
4.125 + }
4.126 + String rn = ve.asType().toString();
4.127 + int last = rn.lastIndexOf('.');
4.128 + if (last >= 0) {
4.129 + rn = rn.substring(last + 1);
4.130 + }
4.131 + if (rn.equals(className)) {
4.132 + params.append(className).append(".this");
4.133 + continue;
4.134 + }
4.135 + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
4.136 + "@On method can only accept String or " + className + " arguments",
4.137 + ee
4.138 + );
4.139 return false;
4.140 }
4.141 - hasParam = true;
4.142 }
4.143 if (!ee.getModifiers().contains(Modifier.STATIC)) {
4.144 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
4.145 @@ -178,17 +234,33 @@
4.146 return false;
4.147 }
4.148 w.append(" OnEvent." + oc.event()).append(".of(").append(cnstnt(id)).
4.149 - append(").perform(new Runnable() { public void run() {\n");
4.150 - w.append(" ").append(type.getSimpleName().toString()).
4.151 - append('.').append(ee.getSimpleName()).append("(");
4.152 - if (hasParam) {
4.153 - w.append("\"").append(id).append("\"");
4.154 - }
4.155 - w.append(");\n");
4.156 - w.append(" }});\n");
4.157 - }
4.158 + append(").perform(new OnDispatch(" + dispatchCnt + "));\n");
4.159 +
4.160 + dispatch.
4.161 + append(" case ").append(dispatchCnt).append(": ").
4.162 + append(type.getSimpleName().toString()).
4.163 + append('.').append(ee.getSimpleName()).append("(").
4.164 + append(params).
4.165 + append("); break;\n");
4.166 +
4.167 + dispatchCnt++;
4.168 + }
4.169 }
4.170 }
4.171 + w.append(" }\n");
4.172 + if (dispatchCnt > 0) {
4.173 + w.append("class OnDispatch implements Runnable {\n");
4.174 + w.append(" private final int dispatch;\n");
4.175 + w.append(" OnDispatch(int d) { dispatch = d; }\n");
4.176 + w.append(" public void run() {\n");
4.177 + w.append(" switch (dispatch) {\n");
4.178 + w.append(dispatch);
4.179 + w.append(" }\n");
4.180 + w.append(" }\n");
4.181 + w.append("}\n");
4.182 + }
4.183 +
4.184 +
4.185 }
4.186 return true;
4.187 }
4.188 @@ -235,4 +307,126 @@
4.189 }
4.190 return e.getEnclosingElement();
4.191 }
4.192 +
4.193 + private static void generateProperties(
4.194 + Writer w, Property[] properties, Collection<String> props,
4.195 + Map<String,Collection<String>> deps
4.196 + ) throws IOException {
4.197 + for (Property p : properties) {
4.198 + final String tn = typeName(p);
4.199 + String[] gs = toGetSet(p.name(), tn);
4.200 +
4.201 + w.write("private " + tn + " prop_" + p.name() + ";\n");
4.202 + w.write("public " + tn + " " + gs[0] + "() {\n");
4.203 + w.write(" if (locked) throw new IllegalStateException();\n");
4.204 + w.write(" return prop_" + p.name() + ";\n");
4.205 + w.write("}\n");
4.206 + w.write("public void " + gs[1] + "(" + tn + " v) {\n");
4.207 + w.write(" if (locked) throw new IllegalStateException();\n");
4.208 + w.write(" prop_" + p.name() + " = v;\n");
4.209 + w.write(" if (ko != null) {\n");
4.210 + w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n");
4.211 + final Collection<String> dependants = deps.get(p.name());
4.212 + if (dependants != null) {
4.213 + for (String depProp : dependants) {
4.214 + w.write(" ko.valueHasMutated(\"" + depProp + "\");\n");
4.215 + }
4.216 + }
4.217 + w.write(" }\n");
4.218 + w.write("}\n");
4.219 +
4.220 + props.add(p.name());
4.221 + props.add(gs[2]);
4.222 + props.add(gs[3]);
4.223 + props.add(gs[0]);
4.224 + }
4.225 + }
4.226 +
4.227 + private boolean generateComputedProperties(
4.228 + Writer w, Collection<? extends Element> arr, Collection<String> props,
4.229 + Map<String,Collection<String>> deps
4.230 + ) throws IOException {
4.231 + for (Element e : arr) {
4.232 + if (e.getKind() != ElementKind.METHOD) {
4.233 + continue;
4.234 + }
4.235 + if (e.getAnnotation(ComputedProperty.class) == null) {
4.236 + continue;
4.237 + }
4.238 + ExecutableElement ee = (ExecutableElement)e;
4.239 + final String tn = ee.getReturnType().toString();
4.240 + final String sn = ee.getSimpleName().toString();
4.241 + String[] gs = toGetSet(sn, tn);
4.242 +
4.243 + w.write("public " + tn + " " + gs[0] + "() {\n");
4.244 + w.write(" if (locked) throw new IllegalStateException();\n");
4.245 + int arg = 0;
4.246 + for (VariableElement pe : ee.getParameters()) {
4.247 + final String dn = pe.getSimpleName().toString();
4.248 + final String dt = pe.asType().toString();
4.249 + String[] call = toGetSet(dn, dt);
4.250 + w.write(" " + dt + " arg" + (++arg) + " = ");
4.251 + w.write(call[0] + "();\n");
4.252 +
4.253 + Collection<String> depends = deps.get(dn);
4.254 + if (depends == null) {
4.255 + depends = new LinkedHashSet<String>();
4.256 + deps.put(dn, depends);
4.257 + }
4.258 + depends.add(sn);
4.259 + }
4.260 + w.write(" try {\n");
4.261 + w.write(" locked = true;\n");
4.262 + w.write(" return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "(");
4.263 + String sep = "";
4.264 + for (int i = 1; i <= arg; i++) {
4.265 + w.write(sep);
4.266 + w.write("arg" + i);
4.267 + sep = ", ";
4.268 + }
4.269 + w.write(");\n");
4.270 + w.write(" } finally {\n");
4.271 + w.write(" locked = false;\n");
4.272 + w.write(" }\n");
4.273 + w.write("}\n");
4.274 +
4.275 + props.add(e.getSimpleName().toString());
4.276 + props.add(gs[2]);
4.277 + props.add(null);
4.278 + props.add(gs[0]);
4.279 + }
4.280 +
4.281 + return true;
4.282 + }
4.283 +
4.284 + private static String[] toGetSet(String name, String type) {
4.285 + String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
4.286 + String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
4.287 + if ("int".equals(type)) {
4.288 + bck2brwsrType = "I";
4.289 + }
4.290 + if ("double".equals(type)) {
4.291 + bck2brwsrType = "D";
4.292 + }
4.293 + String pref = "get";
4.294 + if ("boolean".equals(type)) {
4.295 + pref = "is";
4.296 + bck2brwsrType = "Z";
4.297 + }
4.298 + final String nu = n.replace('.', '_');
4.299 + return new String[]{
4.300 + pref + n,
4.301 + "set" + n,
4.302 + pref + nu + "__" + bck2brwsrType,
4.303 + "set" + nu + "__V" + bck2brwsrType
4.304 + };
4.305 + }
4.306 +
4.307 + private static String typeName(Property p) {
4.308 + try {
4.309 + return p.type().getName();
4.310 + } catch (MirroredTypeException ex) {
4.311 + return ex.getTypeMirror().toString();
4.312 + }
4.313 + }
4.314 }
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/ComputedProperty.java Wed Jan 23 10:57:05 2013 +0100
5.3 @@ -0,0 +1,38 @@
5.4 +/**
5.5 + * Back 2 Browser Bytecode Translator
5.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
5.7 + *
5.8 + * This program is free software: you can redistribute it and/or modify
5.9 + * it under the terms of the GNU General Public License as published by
5.10 + * the Free Software Foundation, version 2 of the License.
5.11 + *
5.12 + * This program is distributed in the hope that it will be useful,
5.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
5.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5.15 + * GNU General Public License for more details.
5.16 + *
5.17 + * You should have received a copy of the GNU General Public License
5.18 + * along with this program. Look for COPYING file in the top folder.
5.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
5.20 + */
5.21 +package org.apidesign.bck2brwsr.htmlpage.api;
5.22 +
5.23 +import java.lang.annotation.ElementType;
5.24 +import java.lang.annotation.Retention;
5.25 +import java.lang.annotation.RetentionPolicy;
5.26 +import java.lang.annotation.Target;
5.27 +
5.28 +/** Can be used in classes annotated with {@link Page} annotation to
5.29 + * define a derived property. Value of derived property is based on values
5.30 + * of {@link Property} as enumerated by {@link Page#properties()}.
5.31 + * <p>
5.32 + * The name of the derived property is the name of the method. The arguments
5.33 + * of the method define the property names (from {@link Page#properties()} list)
5.34 + * the value of property depends on.
5.35 + *
5.36 + * @author Jaroslav Tulach <jtulach@netbeans.org>
5.37 + */
5.38 +@Retention(RetentionPolicy.SOURCE)
5.39 +@Target(ElementType.METHOD)
5.40 +public @interface ComputedProperty {
5.41 +}
6.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Element.java Tue Jan 22 19:21:34 2013 +0100
6.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Element.java Wed Jan 23 10:57:05 2013 +0100
6.3 @@ -30,6 +30,13 @@
6.4 this.id = id;
6.5 }
6.6
6.7 + /** Id of the element in the document.
6.8 + * @return the id for this element
6.9 + */
6.10 + public String getId() {
6.11 + return id;
6.12 + }
6.13 +
6.14 abstract void dontSubclass();
6.15
6.16 @JavaScriptBody(
6.17 @@ -61,7 +68,8 @@
6.18 body="var e = window.document.getElementById(this.fld_id);\n"
6.19 + "e[ev.fld_id] = function() { r.run__V(); };\n"
6.20 )
6.21 - final native void on(OnEvent ev, Runnable r);
6.22 + final void on(OnEvent ev, Runnable r) {
6.23 + }
6.24
6.25 /** Shows alert message dialog in a browser.
6.26 * @param msg the message to show
7.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnEvent.java Tue Jan 22 19:21:34 2013 +0100
7.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnEvent.java Wed Jan 23 10:57:05 2013 +0100
7.3 @@ -26,6 +26,7 @@
7.4 BLUR("onblur"),
7.5 CAN_PLAY("oncanplay"),
7.6 CAN_PLAY_THROUGH("oncanplaythrough"),
7.7 + CHANGE("onchange"),
7.8 CLICK("onclick"),
7.9 CONTEXT_MENU("oncontextmenu"),
7.10 DBL_CLICK("ondblclick"),
7.11 @@ -82,6 +83,13 @@
7.12 this.id = id;
7.13 }
7.14
7.15 + /** The name of property this event is referenced by from an {@link Element}.
7.16 + * For {@link OnEvent#CHANGE}, it is <code>onchange</code>.
7.17 + */
7.18 + public String getElementPropertyName() {
7.19 + return id;
7.20 + }
7.21 +
7.22 /** What should happen when this even happen on one
7.23 * of associated elements. Continue by calling {@link OnController#perform(java.lang.Runnable)}
7.24 * method.
8.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Page.java Tue Jan 22 19:21:34 2013 +0100
8.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Page.java Wed Jan 23 10:57:05 2013 +0100
8.3 @@ -36,4 +36,7 @@
8.4 * found elements with IDs.
8.5 */
8.6 String className() default "";
8.7 + /** List of properties generated into the page.
8.8 + */
8.9 + Property[] properties() default {};
8.10 }
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java Wed Jan 23 10:57:05 2013 +0100
9.3 @@ -0,0 +1,34 @@
9.4 +/**
9.5 + * Back 2 Browser Bytecode Translator
9.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
9.7 + *
9.8 + * This program is free software: you can redistribute it and/or modify
9.9 + * it under the terms of the GNU General Public License as published by
9.10 + * the Free Software Foundation, version 2 of the License.
9.11 + *
9.12 + * This program is distributed in the hope that it will be useful,
9.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
9.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9.15 + * GNU General Public License for more details.
9.16 + *
9.17 + * You should have received a copy of the GNU General Public License
9.18 + * along with this program. Look for COPYING file in the top folder.
9.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
9.20 + */
9.21 +package org.apidesign.bck2brwsr.htmlpage.api;
9.22 +
9.23 +import java.lang.annotation.Retention;
9.24 +import java.lang.annotation.RetentionPolicy;
9.25 +import java.lang.annotation.Target;
9.26 +
9.27 +/** Represents a property in a generated model of an HTML
9.28 + * {@link Page}.
9.29 + *
9.30 + * @author Jaroslav Tulach <jtulach@netbeans.org>
9.31 + */
9.32 +@Retention(RetentionPolicy.SOURCE)
9.33 +@Target({})
9.34 +public @interface Property {
9.35 + String name();
9.36 + Class<?> type();
9.37 +}
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
10.2 +++ b/javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js Wed Jan 23 10:57:05 2013 +0100
10.3 @@ -0,0 +1,3587 @@
10.4 +// Knockout JavaScript library v2.2.1
10.5 +// (c) Steven Sanderson - http://knockoutjs.com/
10.6 +// License: MIT (http://www.opensource.org/licenses/mit-license.php)
10.7 +
10.8 +(function(){
10.9 +var DEBUG=true;
10.10 +(function(window,document,navigator,jQuery,undefined){
10.11 +!function(factory) {
10.12 + // Support three module loading scenarios
10.13 + if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
10.14 + // [1] CommonJS/Node.js
10.15 + var target = module['exports'] || exports; // module.exports is for Node.js
10.16 + factory(target);
10.17 + } else if (typeof define === 'function' && define['amd']) {
10.18 + // [2] AMD anonymous module
10.19 + define(['exports'], factory);
10.20 + } else {
10.21 + // [3] No module loader (plain <script> tag) - put directly in global namespace
10.22 + factory(window['ko'] = {});
10.23 + }
10.24 +}(function(koExports){
10.25 +// Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
10.26 +// In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
10.27 +var ko = typeof koExports !== 'undefined' ? koExports : {};
10.28 +// Google Closure Compiler helpers (used only to make the minified file smaller)
10.29 +ko.exportSymbol = function(koPath, object) {
10.30 + var tokens = koPath.split(".");
10.31 +
10.32 + // In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable)
10.33 + // At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko)
10.34 + var target = ko;
10.35 +
10.36 + for (var i = 0; i < tokens.length - 1; i++)
10.37 + target = target[tokens[i]];
10.38 + target[tokens[tokens.length - 1]] = object;
10.39 +};
10.40 +ko.exportProperty = function(owner, publicName, object) {
10.41 + owner[publicName] = object;
10.42 +};
10.43 +ko.version = "2.2.1";
10.44 +
10.45 +ko.exportSymbol('version', ko.version);
10.46 +ko.utils = new (function () {
10.47 + var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
10.48 +
10.49 + // Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup)
10.50 + var knownEvents = {}, knownEventTypesByEventName = {};
10.51 + var keyEventTypeName = /Firefox\/2/i.test(navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents';
10.52 + knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
10.53 + knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
10.54 + for (var eventType in knownEvents) {
10.55 + var knownEventsForType = knownEvents[eventType];
10.56 + if (knownEventsForType.length) {
10.57 + for (var i = 0, j = knownEventsForType.length; i < j; i++)
10.58 + knownEventTypesByEventName[knownEventsForType[i]] = eventType;
10.59 + }
10.60 + }
10.61 + var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
10.62 +
10.63 + // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
10.64 + // Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
10.65 + // Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
10.66 + // If there is a future need to detect specific versions of IE10+, we will amend this.
10.67 + var ieVersion = (function() {
10.68 + var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
10.69 +
10.70 + // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
10.71 + while (
10.72 + div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
10.73 + iElems[0]
10.74 + );
10.75 + return version > 4 ? version : undefined;
10.76 + }());
10.77 + var isIe6 = ieVersion === 6,
10.78 + isIe7 = ieVersion === 7;
10.79 +
10.80 + function isClickOnCheckableElement(element, eventType) {
10.81 + if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false;
10.82 + if (eventType.toLowerCase() != "click") return false;
10.83 + var inputType = element.type;
10.84 + return (inputType == "checkbox") || (inputType == "radio");
10.85 + }
10.86 +
10.87 + return {
10.88 + fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
10.89 +
10.90 + arrayForEach: function (array, action) {
10.91 + for (var i = 0, j = array.length; i < j; i++)
10.92 + action(array[i]);
10.93 + },
10.94 +
10.95 + arrayIndexOf: function (array, item) {
10.96 + if (typeof Array.prototype.indexOf == "function")
10.97 + return Array.prototype.indexOf.call(array, item);
10.98 + for (var i = 0, j = array.length; i < j; i++)
10.99 + if (array[i] === item)
10.100 + return i;
10.101 + return -1;
10.102 + },
10.103 +
10.104 + arrayFirst: function (array, predicate, predicateOwner) {
10.105 + for (var i = 0, j = array.length; i < j; i++)
10.106 + if (predicate.call(predicateOwner, array[i]))
10.107 + return array[i];
10.108 + return null;
10.109 + },
10.110 +
10.111 + arrayRemoveItem: function (array, itemToRemove) {
10.112 + var index = ko.utils.arrayIndexOf(array, itemToRemove);
10.113 + if (index >= 0)
10.114 + array.splice(index, 1);
10.115 + },
10.116 +
10.117 + arrayGetDistinctValues: function (array) {
10.118 + array = array || [];
10.119 + var result = [];
10.120 + for (var i = 0, j = array.length; i < j; i++) {
10.121 + if (ko.utils.arrayIndexOf(result, array[i]) < 0)
10.122 + result.push(array[i]);
10.123 + }
10.124 + return result;
10.125 + },
10.126 +
10.127 + arrayMap: function (array, mapping) {
10.128 + array = array || [];
10.129 + var result = [];
10.130 + for (var i = 0, j = array.length; i < j; i++)
10.131 + result.push(mapping(array[i]));
10.132 + return result;
10.133 + },
10.134 +
10.135 + arrayFilter: function (array, predicate) {
10.136 + array = array || [];
10.137 + var result = [];
10.138 + for (var i = 0, j = array.length; i < j; i++)
10.139 + if (predicate(array[i]))
10.140 + result.push(array[i]);
10.141 + return result;
10.142 + },
10.143 +
10.144 + arrayPushAll: function (array, valuesToPush) {
10.145 + if (valuesToPush instanceof Array)
10.146 + array.push.apply(array, valuesToPush);
10.147 + else
10.148 + for (var i = 0, j = valuesToPush.length; i < j; i++)
10.149 + array.push(valuesToPush[i]);
10.150 + return array;
10.151 + },
10.152 +
10.153 + extend: function (target, source) {
10.154 + if (source) {
10.155 + for(var prop in source) {
10.156 + if(source.hasOwnProperty(prop)) {
10.157 + target[prop] = source[prop];
10.158 + }
10.159 + }
10.160 + }
10.161 + return target;
10.162 + },
10.163 +
10.164 + emptyDomNode: function (domNode) {
10.165 + while (domNode.firstChild) {
10.166 + ko.removeNode(domNode.firstChild);
10.167 + }
10.168 + },
10.169 +
10.170 + moveCleanedNodesToContainerElement: function(nodes) {
10.171 + // Ensure it's a real array, as we're about to reparent the nodes and
10.172 + // we don't want the underlying collection to change while we're doing that.
10.173 + var nodesArray = ko.utils.makeArray(nodes);
10.174 +
10.175 + var container = document.createElement('div');
10.176 + for (var i = 0, j = nodesArray.length; i < j; i++) {
10.177 + container.appendChild(ko.cleanNode(nodesArray[i]));
10.178 + }
10.179 + return container;
10.180 + },
10.181 +
10.182 + cloneNodes: function (nodesArray, shouldCleanNodes) {
10.183 + for (var i = 0, j = nodesArray.length, newNodesArray = []; i < j; i++) {
10.184 + var clonedNode = nodesArray[i].cloneNode(true);
10.185 + newNodesArray.push(shouldCleanNodes ? ko.cleanNode(clonedNode) : clonedNode);
10.186 + }
10.187 + return newNodesArray;
10.188 + },
10.189 +
10.190 + setDomNodeChildren: function (domNode, childNodes) {
10.191 + ko.utils.emptyDomNode(domNode);
10.192 + if (childNodes) {
10.193 + for (var i = 0, j = childNodes.length; i < j; i++)
10.194 + domNode.appendChild(childNodes[i]);
10.195 + }
10.196 + },
10.197 +
10.198 + replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
10.199 + var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
10.200 + if (nodesToReplaceArray.length > 0) {
10.201 + var insertionPoint = nodesToReplaceArray[0];
10.202 + var parent = insertionPoint.parentNode;
10.203 + for (var i = 0, j = newNodesArray.length; i < j; i++)
10.204 + parent.insertBefore(newNodesArray[i], insertionPoint);
10.205 + for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
10.206 + ko.removeNode(nodesToReplaceArray[i]);
10.207 + }
10.208 + }
10.209 + },
10.210 +
10.211 + setOptionNodeSelectionState: function (optionNode, isSelected) {
10.212 + // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
10.213 + if (ieVersion < 7)
10.214 + optionNode.setAttribute("selected", isSelected);
10.215 + else
10.216 + optionNode.selected = isSelected;
10.217 + },
10.218 +
10.219 + stringTrim: function (string) {
10.220 + return (string || "").replace(stringTrimRegex, "");
10.221 + },
10.222 +
10.223 + stringTokenize: function (string, delimiter) {
10.224 + var result = [];
10.225 + var tokens = (string || "").split(delimiter);
10.226 + for (var i = 0, j = tokens.length; i < j; i++) {
10.227 + var trimmed = ko.utils.stringTrim(tokens[i]);
10.228 + if (trimmed !== "")
10.229 + result.push(trimmed);
10.230 + }
10.231 + return result;
10.232 + },
10.233 +
10.234 + stringStartsWith: function (string, startsWith) {
10.235 + string = string || "";
10.236 + if (startsWith.length > string.length)
10.237 + return false;
10.238 + return string.substring(0, startsWith.length) === startsWith;
10.239 + },
10.240 +
10.241 + domNodeIsContainedBy: function (node, containedByNode) {
10.242 + if (containedByNode.compareDocumentPosition)
10.243 + return (containedByNode.compareDocumentPosition(node) & 16) == 16;
10.244 + while (node != null) {
10.245 + if (node == containedByNode)
10.246 + return true;
10.247 + node = node.parentNode;
10.248 + }
10.249 + return false;
10.250 + },
10.251 +
10.252 + domNodeIsAttachedToDocument: function (node) {
10.253 + return ko.utils.domNodeIsContainedBy(node, node.ownerDocument);
10.254 + },
10.255 +
10.256 + tagNameLower: function(element) {
10.257 + // For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case.
10.258 + // Possible future optimization: If we know it's an element from an XHTML document (not HTML),
10.259 + // we don't need to do the .toLowerCase() as it will always be lower case anyway.
10.260 + return element && element.tagName && element.tagName.toLowerCase();
10.261 + },
10.262 +
10.263 + registerEventHandler: function (element, eventType, handler) {
10.264 + var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
10.265 + if (!mustUseAttachEvent && typeof jQuery != "undefined") {
10.266 + if (isClickOnCheckableElement(element, eventType)) {
10.267 + // For click events on checkboxes, jQuery interferes with the event handling in an awkward way:
10.268 + // it toggles the element checked state *after* the click event handlers run, whereas native
10.269 + // click events toggle the checked state *before* the event handler.
10.270 + // Fix this by intecepting the handler and applying the correct checkedness before it runs.
10.271 + var originalHandler = handler;
10.272 + handler = function(event, eventData) {
10.273 + var jQuerySuppliedCheckedState = this.checked;
10.274 + if (eventData)
10.275 + this.checked = eventData.checkedStateBeforeEvent !== true;
10.276 + originalHandler.call(this, event);
10.277 + this.checked = jQuerySuppliedCheckedState; // Restore the state jQuery applied
10.278 + };
10.279 + }
10.280 + jQuery(element)['bind'](eventType, handler);
10.281 + } else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
10.282 + element.addEventListener(eventType, handler, false);
10.283 + else if (typeof element.attachEvent != "undefined")
10.284 + element.attachEvent("on" + eventType, function (event) {
10.285 + handler.call(element, event);
10.286 + });
10.287 + else
10.288 + throw new Error("Browser doesn't support addEventListener or attachEvent");
10.289 + },
10.290 +
10.291 + triggerEvent: function (element, eventType) {
10.292 + if (!(element && element.nodeType))
10.293 + throw new Error("element must be a DOM node when calling triggerEvent");
10.294 +
10.295 + if (typeof jQuery != "undefined") {
10.296 + var eventData = [];
10.297 + if (isClickOnCheckableElement(element, eventType)) {
10.298 + // Work around the jQuery "click events on checkboxes" issue described above by storing the original checked state before triggering the handler
10.299 + eventData.push({ checkedStateBeforeEvent: element.checked });
10.300 + }
10.301 + jQuery(element)['trigger'](eventType, eventData);
10.302 + } else if (typeof document.createEvent == "function") {
10.303 + if (typeof element.dispatchEvent == "function") {
10.304 + var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
10.305 + var event = document.createEvent(eventCategory);
10.306 + event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
10.307 + element.dispatchEvent(event);
10.308 + }
10.309 + else
10.310 + throw new Error("The supplied element doesn't support dispatchEvent");
10.311 + } else if (typeof element.fireEvent != "undefined") {
10.312 + // Unlike other browsers, IE doesn't change the checked state of checkboxes/radiobuttons when you trigger their "click" event
10.313 + // so to make it consistent, we'll do it manually here
10.314 + if (isClickOnCheckableElement(element, eventType))
10.315 + element.checked = element.checked !== true;
10.316 + element.fireEvent("on" + eventType);
10.317 + }
10.318 + else
10.319 + throw new Error("Browser doesn't support triggering events");
10.320 + },
10.321 +
10.322 + unwrapObservable: function (value) {
10.323 + return ko.isObservable(value) ? value() : value;
10.324 + },
10.325 +
10.326 + peekObservable: function (value) {
10.327 + return ko.isObservable(value) ? value.peek() : value;
10.328 + },
10.329 +
10.330 + toggleDomNodeCssClass: function (node, classNames, shouldHaveClass) {
10.331 + if (classNames) {
10.332 + var cssClassNameRegex = /[\w-]+/g,
10.333 + currentClassNames = node.className.match(cssClassNameRegex) || [];
10.334 + ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
10.335 + var indexOfClass = ko.utils.arrayIndexOf(currentClassNames, className);
10.336 + if (indexOfClass >= 0) {
10.337 + if (!shouldHaveClass)
10.338 + currentClassNames.splice(indexOfClass, 1);
10.339 + } else {
10.340 + if (shouldHaveClass)
10.341 + currentClassNames.push(className);
10.342 + }
10.343 + });
10.344 + node.className = currentClassNames.join(" ");
10.345 + }
10.346 + },
10.347 +
10.348 + setTextContent: function(element, textContent) {
10.349 + var value = ko.utils.unwrapObservable(textContent);
10.350 + if ((value === null) || (value === undefined))
10.351 + value = "";
10.352 +
10.353 + if (element.nodeType === 3) {
10.354 + element.data = value;
10.355 + } else {
10.356 + // We need there to be exactly one child: a text node.
10.357 + // If there are no children, more than one, or if it's not a text node,
10.358 + // we'll clear everything and create a single text node.
10.359 + var innerTextNode = ko.virtualElements.firstChild(element);
10.360 + if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) {
10.361 + ko.virtualElements.setDomNodeChildren(element, [document.createTextNode(value)]);
10.362 + } else {
10.363 + innerTextNode.data = value;
10.364 + }
10.365 +
10.366 + ko.utils.forceRefresh(element);
10.367 + }
10.368 + },
10.369 +
10.370 + setElementName: function(element, name) {
10.371 + element.name = name;
10.372 +
10.373 + // Workaround IE 6/7 issue
10.374 + // - https://github.com/SteveSanderson/knockout/issues/197
10.375 + // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
10.376 + if (ieVersion <= 7) {
10.377 + try {
10.378 + element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
10.379 + }
10.380 + catch(e) {} // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View"
10.381 + }
10.382 + },
10.383 +
10.384 + forceRefresh: function(node) {
10.385 + // Workaround for an IE9 rendering bug - https://github.com/SteveSanderson/knockout/issues/209
10.386 + if (ieVersion >= 9) {
10.387 + // For text nodes and comment nodes (most likely virtual elements), we will have to refresh the container
10.388 + var elem = node.nodeType == 1 ? node : node.parentNode;
10.389 + if (elem.style)
10.390 + elem.style.zoom = elem.style.zoom;
10.391 + }
10.392 + },
10.393 +
10.394 + ensureSelectElementIsRenderedCorrectly: function(selectElement) {
10.395 + // Workaround for IE9 rendering bug - it doesn't reliably display all the text in dynamically-added select boxes unless you force it to re-render by updating the width.
10.396 + // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
10.397 + if (ieVersion >= 9) {
10.398 + var originalWidth = selectElement.style.width;
10.399 + selectElement.style.width = 0;
10.400 + selectElement.style.width = originalWidth;
10.401 + }
10.402 + },
10.403 +
10.404 + range: function (min, max) {
10.405 + min = ko.utils.unwrapObservable(min);
10.406 + max = ko.utils.unwrapObservable(max);
10.407 + var result = [];
10.408 + for (var i = min; i <= max; i++)
10.409 + result.push(i);
10.410 + return result;
10.411 + },
10.412 +
10.413 + makeArray: function(arrayLikeObject) {
10.414 + var result = [];
10.415 + for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
10.416 + result.push(arrayLikeObject[i]);
10.417 + };
10.418 + return result;
10.419 + },
10.420 +
10.421 + isIe6 : isIe6,
10.422 + isIe7 : isIe7,
10.423 + ieVersion : ieVersion,
10.424 +
10.425 + getFormFields: function(form, fieldName) {
10.426 + var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
10.427 + var isMatchingField = (typeof fieldName == 'string')
10.428 + ? function(field) { return field.name === fieldName }
10.429 + : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
10.430 + var matches = [];
10.431 + for (var i = fields.length - 1; i >= 0; i--) {
10.432 + if (isMatchingField(fields[i]))
10.433 + matches.push(fields[i]);
10.434 + };
10.435 + return matches;
10.436 + },
10.437 +
10.438 + parseJson: function (jsonString) {
10.439 + if (typeof jsonString == "string") {
10.440 + jsonString = ko.utils.stringTrim(jsonString);
10.441 + if (jsonString) {
10.442 + if (window.JSON && window.JSON.parse) // Use native parsing where available
10.443 + return window.JSON.parse(jsonString);
10.444 + return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
10.445 + }
10.446 + }
10.447 + return null;
10.448 + },
10.449 +
10.450 + stringifyJson: function (data, replacer, space) { // replacer and space are optional
10.451 + if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined"))
10.452 + throw new Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
10.453 + return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
10.454 + },
10.455 +
10.456 + postJson: function (urlOrForm, data, options) {
10.457 + options = options || {};
10.458 + var params = options['params'] || {};
10.459 + var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
10.460 + var url = urlOrForm;
10.461 +
10.462 + // If we were given a form, use its 'action' URL and pick out any requested field values
10.463 + if((typeof urlOrForm == 'object') && (ko.utils.tagNameLower(urlOrForm) === "form")) {
10.464 + var originalForm = urlOrForm;
10.465 + url = originalForm.action;
10.466 + for (var i = includeFields.length - 1; i >= 0; i--) {
10.467 + var fields = ko.utils.getFormFields(originalForm, includeFields[i]);
10.468 + for (var j = fields.length - 1; j >= 0; j--)
10.469 + params[fields[j].name] = fields[j].value;
10.470 + }
10.471 + }
10.472 +
10.473 + data = ko.utils.unwrapObservable(data);
10.474 + var form = document.createElement("form");
10.475 + form.style.display = "none";
10.476 + form.action = url;
10.477 + form.method = "post";
10.478 + for (var key in data) {
10.479 + var input = document.createElement("input");
10.480 + input.name = key;
10.481 + input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
10.482 + form.appendChild(input);
10.483 + }
10.484 + for (var key in params) {
10.485 + var input = document.createElement("input");
10.486 + input.name = key;
10.487 + input.value = params[key];
10.488 + form.appendChild(input);
10.489 + }
10.490 + document.body.appendChild(form);
10.491 + options['submitter'] ? options['submitter'](form) : form.submit();
10.492 + setTimeout(function () { form.parentNode.removeChild(form); }, 0);
10.493 + }
10.494 + }
10.495 +})();
10.496 +
10.497 +ko.exportSymbol('utils', ko.utils);
10.498 +ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach);
10.499 +ko.exportSymbol('utils.arrayFirst', ko.utils.arrayFirst);
10.500 +ko.exportSymbol('utils.arrayFilter', ko.utils.arrayFilter);
10.501 +ko.exportSymbol('utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues);
10.502 +ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
10.503 +ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
10.504 +ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
10.505 +ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
10.506 +ko.exportSymbol('utils.extend', ko.utils.extend);
10.507 +ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
10.508 +ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
10.509 +ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable);
10.510 +ko.exportSymbol('utils.postJson', ko.utils.postJson);
10.511 +ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
10.512 +ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler);
10.513 +ko.exportSymbol('utils.stringifyJson', ko.utils.stringifyJson);
10.514 +ko.exportSymbol('utils.range', ko.utils.range);
10.515 +ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);
10.516 +ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
10.517 +ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
10.518 +
10.519 +if (!Function.prototype['bind']) {
10.520 + // Function.prototype.bind is a standard part of ECMAScript 5th Edition (December 2009, http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf)
10.521 + // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
10.522 + Function.prototype['bind'] = function (object) {
10.523 + var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift();
10.524 + return function () {
10.525 + return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
10.526 + };
10.527 + };
10.528 +}
10.529 +
10.530 +ko.utils.domData = new (function () {
10.531 + var uniqueId = 0;
10.532 + var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
10.533 + var dataStore = {};
10.534 + return {
10.535 + get: function (node, key) {
10.536 + var allDataForNode = ko.utils.domData.getAll(node, false);
10.537 + return allDataForNode === undefined ? undefined : allDataForNode[key];
10.538 + },
10.539 + set: function (node, key, value) {
10.540 + if (value === undefined) {
10.541 + // Make sure we don't actually create a new domData key if we are actually deleting a value
10.542 + if (ko.utils.domData.getAll(node, false) === undefined)
10.543 + return;
10.544 + }
10.545 + var allDataForNode = ko.utils.domData.getAll(node, true);
10.546 + allDataForNode[key] = value;
10.547 + },
10.548 + getAll: function (node, createIfNotFound) {
10.549 + var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
10.550 + var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];
10.551 + if (!hasExistingDataStore) {
10.552 + if (!createIfNotFound)
10.553 + return undefined;
10.554 + dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
10.555 + dataStore[dataStoreKey] = {};
10.556 + }
10.557 + return dataStore[dataStoreKey];
10.558 + },
10.559 + clear: function (node) {
10.560 + var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
10.561 + if (dataStoreKey) {
10.562 + delete dataStore[dataStoreKey];
10.563 + node[dataStoreKeyExpandoPropertyName] = null;
10.564 + return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
10.565 + }
10.566 + return false;
10.567 + }
10.568 + }
10.569 +})();
10.570 +
10.571 +ko.exportSymbol('utils.domData', ko.utils.domData);
10.572 +ko.exportSymbol('utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully
10.573 +
10.574 +ko.utils.domNodeDisposal = new (function () {
10.575 + var domDataKey = "__ko_domNodeDisposal__" + (new Date).getTime();
10.576 + var cleanableNodeTypes = { 1: true, 8: true, 9: true }; // Element, Comment, Document
10.577 + var cleanableNodeTypesWithDescendants = { 1: true, 9: true }; // Element, Document
10.578 +
10.579 + function getDisposeCallbacksCollection(node, createIfNotFound) {
10.580 + var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);
10.581 + if ((allDisposeCallbacks === undefined) && createIfNotFound) {
10.582 + allDisposeCallbacks = [];
10.583 + ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);
10.584 + }
10.585 + return allDisposeCallbacks;
10.586 + }
10.587 + function destroyCallbacksCollection(node) {
10.588 + ko.utils.domData.set(node, domDataKey, undefined);
10.589 + }
10.590 +
10.591 + function cleanSingleNode(node) {
10.592 + // Run all the dispose callbacks
10.593 + var callbacks = getDisposeCallbacksCollection(node, false);
10.594 + if (callbacks) {
10.595 + callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
10.596 + for (var i = 0; i < callbacks.length; i++)
10.597 + callbacks[i](node);
10.598 + }
10.599 +
10.600 + // Also erase the DOM data
10.601 + ko.utils.domData.clear(node);
10.602 +
10.603 + // Special support for jQuery here because it's so commonly used.
10.604 + // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
10.605 + // so notify it to tear down any resources associated with the node & descendants here.
10.606 + if ((typeof jQuery == "function") && (typeof jQuery['cleanData'] == "function"))
10.607 + jQuery['cleanData']([node]);
10.608 +
10.609 + // Also clear any immediate-child comment nodes, as these wouldn't have been found by
10.610 + // node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
10.611 + if (cleanableNodeTypesWithDescendants[node.nodeType])
10.612 + cleanImmediateCommentTypeChildren(node);
10.613 + }
10.614 +
10.615 + function cleanImmediateCommentTypeChildren(nodeWithChildren) {
10.616 + var child, nextChild = nodeWithChildren.firstChild;
10.617 + while (child = nextChild) {
10.618 + nextChild = child.nextSibling;
10.619 + if (child.nodeType === 8)
10.620 + cleanSingleNode(child);
10.621 + }
10.622 + }
10.623 +
10.624 + return {
10.625 + addDisposeCallback : function(node, callback) {
10.626 + if (typeof callback != "function")
10.627 + throw new Error("Callback must be a function");
10.628 + getDisposeCallbacksCollection(node, true).push(callback);
10.629 + },
10.630 +
10.631 + removeDisposeCallback : function(node, callback) {
10.632 + var callbacksCollection = getDisposeCallbacksCollection(node, false);
10.633 + if (callbacksCollection) {
10.634 + ko.utils.arrayRemoveItem(callbacksCollection, callback);
10.635 + if (callbacksCollection.length == 0)
10.636 + destroyCallbacksCollection(node);
10.637 + }
10.638 + },
10.639 +
10.640 + cleanNode : function(node) {
10.641 + // First clean this node, where applicable
10.642 + if (cleanableNodeTypes[node.nodeType]) {
10.643 + cleanSingleNode(node);
10.644 +
10.645 + // ... then its descendants, where applicable
10.646 + if (cleanableNodeTypesWithDescendants[node.nodeType]) {
10.647 + // Clone the descendants list in case it changes during iteration
10.648 + var descendants = [];
10.649 + ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
10.650 + for (var i = 0, j = descendants.length; i < j; i++)
10.651 + cleanSingleNode(descendants[i]);
10.652 + }
10.653 + }
10.654 + return node;
10.655 + },
10.656 +
10.657 + removeNode : function(node) {
10.658 + ko.cleanNode(node);
10.659 + if (node.parentNode)
10.660 + node.parentNode.removeChild(node);
10.661 + }
10.662 + }
10.663 +})();
10.664 +ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience
10.665 +ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience
10.666 +ko.exportSymbol('cleanNode', ko.cleanNode);
10.667 +ko.exportSymbol('removeNode', ko.removeNode);
10.668 +ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal);
10.669 +ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);
10.670 +ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);
10.671 +(function () {
10.672 + var leadingCommentRegex = /^(\s*)<!--(.*?)-->/;
10.673 +
10.674 + function simpleHtmlParse(html) {
10.675 + // Based on jQuery's "clean" function, but only accounting for table-related elements.
10.676 + // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly
10.677 +
10.678 + // Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of
10.679 + // a descendant node. For example: "<div><!-- mycomment -->abc</div>" will get parsed as "<div>abc</div>"
10.680 + // This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node
10.681 + // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
10.682 +
10.683 + // Trim whitespace, otherwise indexOf won't work as expected
10.684 + var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div");
10.685 +
10.686 + // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column
10.687 + var wrap = tags.match(/^<(thead|tbody|tfoot)/) && [1, "<table>", "</table>"] ||
10.688 + !tags.indexOf("<tr") && [2, "<table><tbody>", "</tbody></table>"] ||
10.689 + (!tags.indexOf("<td") || !tags.indexOf("<th")) && [3, "<table><tbody><tr>", "</tr></tbody></table>"] ||
10.690 + /* anything else */ [0, "", ""];
10.691 +
10.692 + // Go to html and back, then peel off extra wrappers
10.693 + // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
10.694 + var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
10.695 + if (typeof window['innerShiv'] == "function") {
10.696 + div.appendChild(window['innerShiv'](markup));
10.697 + } else {
10.698 + div.innerHTML = markup;
10.699 + }
10.700 +
10.701 + // Move to the right depth
10.702 + while (wrap[0]--)
10.703 + div = div.lastChild;
10.704 +
10.705 + return ko.utils.makeArray(div.lastChild.childNodes);
10.706 + }
10.707 +
10.708 + function jQueryHtmlParse(html) {
10.709 + // jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API.
10.710 + if (jQuery['parseHTML']) {
10.711 + return jQuery['parseHTML'](html);
10.712 + } else {
10.713 + // For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function.
10.714 + var elems = jQuery['clean']([html]);
10.715 +
10.716 + // As of jQuery 1.7.1, jQuery parses the HTML by appending it to some dummy parent nodes held in an in-memory document fragment.
10.717 + // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
10.718 + // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.
10.719 + if (elems && elems[0]) {
10.720 + // Find the top-most parent element that's a direct child of a document fragment
10.721 + var elem = elems[0];
10.722 + while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)
10.723 + elem = elem.parentNode;
10.724 + // ... then detach it
10.725 + if (elem.parentNode)
10.726 + elem.parentNode.removeChild(elem);
10.727 + }
10.728 +
10.729 + return elems;
10.730 + }
10.731 + }
10.732 +
10.733 + ko.utils.parseHtmlFragment = function(html) {
10.734 + return typeof jQuery != 'undefined' ? jQueryHtmlParse(html) // As below, benefit from jQuery's optimisations where possible
10.735 + : simpleHtmlParse(html); // ... otherwise, this simple logic will do in most common cases.
10.736 + };
10.737 +
10.738 + ko.utils.setHtml = function(node, html) {
10.739 + ko.utils.emptyDomNode(node);
10.740 +
10.741 + // There's no legitimate reason to display a stringified observable without unwrapping it, so we'll unwrap it
10.742 + html = ko.utils.unwrapObservable(html);
10.743 +
10.744 + if ((html !== null) && (html !== undefined)) {
10.745 + if (typeof html != 'string')
10.746 + html = html.toString();
10.747 +
10.748 + // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
10.749 + // for example <tr> elements which are not normally allowed to exist on their own.
10.750 + // If you've referenced jQuery we'll use that rather than duplicating its code.
10.751 + if (typeof jQuery != 'undefined') {
10.752 + jQuery(node)['html'](html);
10.753 + } else {
10.754 + // ... otherwise, use KO's own parsing logic.
10.755 + var parsedNodes = ko.utils.parseHtmlFragment(html);
10.756 + for (var i = 0; i < parsedNodes.length; i++)
10.757 + node.appendChild(parsedNodes[i]);
10.758 + }
10.759 + }
10.760 + };
10.761 +})();
10.762 +
10.763 +ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
10.764 +ko.exportSymbol('utils.setHtml', ko.utils.setHtml);
10.765 +
10.766 +ko.memoization = (function () {
10.767 + var memos = {};
10.768 +
10.769 + function randomMax8HexChars() {
10.770 + return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
10.771 + }
10.772 + function generateRandomId() {
10.773 + return randomMax8HexChars() + randomMax8HexChars();
10.774 + }
10.775 + function findMemoNodes(rootNode, appendToArray) {
10.776 + if (!rootNode)
10.777 + return;
10.778 + if (rootNode.nodeType == 8) {
10.779 + var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
10.780 + if (memoId != null)
10.781 + appendToArray.push({ domNode: rootNode, memoId: memoId });
10.782 + } else if (rootNode.nodeType == 1) {
10.783 + for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)
10.784 + findMemoNodes(childNodes[i], appendToArray);
10.785 + }
10.786 + }
10.787 +
10.788 + return {
10.789 + memoize: function (callback) {
10.790 + if (typeof callback != "function")
10.791 + throw new Error("You can only pass a function to ko.memoization.memoize()");
10.792 + var memoId = generateRandomId();
10.793 + memos[memoId] = callback;
10.794 + return "<!--[ko_memo:" + memoId + "]-->";
10.795 + },
10.796 +
10.797 + unmemoize: function (memoId, callbackParams) {
10.798 + var callback = memos[memoId];
10.799 + if (callback === undefined)
10.800 + throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");
10.801 + try {
10.802 + callback.apply(null, callbackParams || []);
10.803 + return true;
10.804 + }
10.805 + finally { delete memos[memoId]; }
10.806 + },
10.807 +
10.808 + unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {
10.809 + var memos = [];
10.810 + findMemoNodes(domNode, memos);
10.811 + for (var i = 0, j = memos.length; i < j; i++) {
10.812 + var node = memos[i].domNode;
10.813 + var combinedParams = [node];
10.814 + if (extraCallbackParamsArray)
10.815 + ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray);
10.816 + ko.memoization.unmemoize(memos[i].memoId, combinedParams);
10.817 + node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
10.818 + if (node.parentNode)
10.819 + node.parentNode.removeChild(node); // If possible, erase it totally (not always possible - someone else might just hold a reference to it then call unmemoizeDomNodeAndDescendants again)
10.820 + }
10.821 + },
10.822 +
10.823 + parseMemoText: function (memoText) {
10.824 + var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
10.825 + return match ? match[1] : null;
10.826 + }
10.827 + };
10.828 +})();
10.829 +
10.830 +ko.exportSymbol('memoization', ko.memoization);
10.831 +ko.exportSymbol('memoization.memoize', ko.memoization.memoize);
10.832 +ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize);
10.833 +ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText);
10.834 +ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);
10.835 +ko.extenders = {
10.836 + 'throttle': function(target, timeout) {
10.837 + // Throttling means two things:
10.838 +
10.839 + // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies
10.840 + // notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate
10.841 + target['throttleEvaluation'] = timeout;
10.842 +
10.843 + // (2) For writable targets (observables, or writable dependent observables), we throttle *writes*
10.844 + // so the target cannot change value synchronously or faster than a certain rate
10.845 + var writeTimeoutInstance = null;
10.846 + return ko.dependentObservable({
10.847 + 'read': target,
10.848 + 'write': function(value) {
10.849 + clearTimeout(writeTimeoutInstance);
10.850 + writeTimeoutInstance = setTimeout(function() {
10.851 + target(value);
10.852 + }, timeout);
10.853 + }
10.854 + });
10.855 + },
10.856 +
10.857 + 'notify': function(target, notifyWhen) {
10.858 + target["equalityComparer"] = notifyWhen == "always"
10.859 + ? function() { return false } // Treat all values as not equal
10.860 + : ko.observable["fn"]["equalityComparer"];
10.861 + return target;
10.862 + }
10.863 +};
10.864 +
10.865 +function applyExtenders(requestedExtenders) {
10.866 + var target = this;
10.867 + if (requestedExtenders) {
10.868 + for (var key in requestedExtenders) {
10.869 + var extenderHandler = ko.extenders[key];
10.870 + if (typeof extenderHandler == 'function') {
10.871 + target = extenderHandler(target, requestedExtenders[key]);
10.872 + }
10.873 + }
10.874 + }
10.875 + return target;
10.876 +}
10.877 +
10.878 +ko.exportSymbol('extenders', ko.extenders);
10.879 +
10.880 +ko.subscription = function (target, callback, disposeCallback) {
10.881 + this.target = target;
10.882 + this.callback = callback;
10.883 + this.disposeCallback = disposeCallback;
10.884 + ko.exportProperty(this, 'dispose', this.dispose);
10.885 +};
10.886 +ko.subscription.prototype.dispose = function () {
10.887 + this.isDisposed = true;
10.888 + this.disposeCallback();
10.889 +};
10.890 +
10.891 +ko.subscribable = function () {
10.892 + this._subscriptions = {};
10.893 +
10.894 + ko.utils.extend(this, ko.subscribable['fn']);
10.895 + ko.exportProperty(this, 'subscribe', this.subscribe);
10.896 + ko.exportProperty(this, 'extend', this.extend);
10.897 + ko.exportProperty(this, 'getSubscriptionsCount', this.getSubscriptionsCount);
10.898 +}
10.899 +
10.900 +var defaultEvent = "change";
10.901 +
10.902 +ko.subscribable['fn'] = {
10.903 + subscribe: function (callback, callbackTarget, event) {
10.904 + event = event || defaultEvent;
10.905 + var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
10.906 +
10.907 + var subscription = new ko.subscription(this, boundCallback, function () {
10.908 + ko.utils.arrayRemoveItem(this._subscriptions[event], subscription);
10.909 + }.bind(this));
10.910 +
10.911 + if (!this._subscriptions[event])
10.912 + this._subscriptions[event] = [];
10.913 + this._subscriptions[event].push(subscription);
10.914 + return subscription;
10.915 + },
10.916 +
10.917 + "notifySubscribers": function (valueToNotify, event) {
10.918 + event = event || defaultEvent;
10.919 + if (this._subscriptions[event]) {
10.920 + ko.dependencyDetection.ignore(function() {
10.921 + ko.utils.arrayForEach(this._subscriptions[event].slice(0), function (subscription) {
10.922 + // In case a subscription was disposed during the arrayForEach cycle, check
10.923 + // for isDisposed on each subscription before invoking its callback
10.924 + if (subscription && (subscription.isDisposed !== true))
10.925 + subscription.callback(valueToNotify);
10.926 + });
10.927 + }, this);
10.928 + }
10.929 + },
10.930 +
10.931 + getSubscriptionsCount: function () {
10.932 + var total = 0;
10.933 + for (var eventName in this._subscriptions) {
10.934 + if (this._subscriptions.hasOwnProperty(eventName))
10.935 + total += this._subscriptions[eventName].length;
10.936 + }
10.937 + return total;
10.938 + },
10.939 +
10.940 + extend: applyExtenders
10.941 +};
10.942 +
10.943 +
10.944 +ko.isSubscribable = function (instance) {
10.945 + return typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
10.946 +};
10.947 +
10.948 +ko.exportSymbol('subscribable', ko.subscribable);
10.949 +ko.exportSymbol('isSubscribable', ko.isSubscribable);
10.950 +
10.951 +ko.dependencyDetection = (function () {
10.952 + var _frames = [];
10.953 +
10.954 + return {
10.955 + begin: function (callback) {
10.956 + _frames.push({ callback: callback, distinctDependencies:[] });
10.957 + },
10.958 +
10.959 + end: function () {
10.960 + _frames.pop();
10.961 + },
10.962 +
10.963 + registerDependency: function (subscribable) {
10.964 + if (!ko.isSubscribable(subscribable))
10.965 + throw new Error("Only subscribable things can act as dependencies");
10.966 + if (_frames.length > 0) {
10.967 + var topFrame = _frames[_frames.length - 1];
10.968 + if (!topFrame || ko.utils.arrayIndexOf(topFrame.distinctDependencies, subscribable) >= 0)
10.969 + return;
10.970 + topFrame.distinctDependencies.push(subscribable);
10.971 + topFrame.callback(subscribable);
10.972 + }
10.973 + },
10.974 +
10.975 + ignore: function(callback, callbackTarget, callbackArgs) {
10.976 + try {
10.977 + _frames.push(null);
10.978 + return callback.apply(callbackTarget, callbackArgs || []);
10.979 + } finally {
10.980 + _frames.pop();
10.981 + }
10.982 + }
10.983 + };
10.984 +})();
10.985 +var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true };
10.986 +
10.987 +ko.observable = function (initialValue) {
10.988 + var _latestValue = initialValue;
10.989 +
10.990 + function observable() {
10.991 + if (arguments.length > 0) {
10.992 + // Write
10.993 +
10.994 + // Ignore writes if the value hasn't changed
10.995 + if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
10.996 + observable.valueWillMutate();
10.997 + _latestValue = arguments[0];
10.998 + if (DEBUG) observable._latestValue = _latestValue;
10.999 + observable.valueHasMutated();
10.1000 + }
10.1001 + return this; // Permits chained assignments
10.1002 + }
10.1003 + else {
10.1004 + // Read
10.1005 + ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
10.1006 + return _latestValue;
10.1007 + }
10.1008 + }
10.1009 + if (DEBUG) observable._latestValue = _latestValue;
10.1010 + ko.subscribable.call(observable);
10.1011 + observable.peek = function() { return _latestValue };
10.1012 + observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }
10.1013 + observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }
10.1014 + ko.utils.extend(observable, ko.observable['fn']);
10.1015 +
10.1016 + ko.exportProperty(observable, 'peek', observable.peek);
10.1017 + ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
10.1018 + ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
10.1019 +
10.1020 + return observable;
10.1021 +}
10.1022 +
10.1023 +ko.observable['fn'] = {
10.1024 + "equalityComparer": function valuesArePrimitiveAndEqual(a, b) {
10.1025 + var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
10.1026 + return oldValueIsPrimitive ? (a === b) : false;
10.1027 + }
10.1028 +};
10.1029 +
10.1030 +var protoProperty = ko.observable.protoProperty = "__ko_proto__";
10.1031 +ko.observable['fn'][protoProperty] = ko.observable;
10.1032 +
10.1033 +ko.hasPrototype = function(instance, prototype) {
10.1034 + if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
10.1035 + if (instance[protoProperty] === prototype) return true;
10.1036 + return ko.hasPrototype(instance[protoProperty], prototype); // Walk the prototype chain
10.1037 +};
10.1038 +
10.1039 +ko.isObservable = function (instance) {
10.1040 + return ko.hasPrototype(instance, ko.observable);
10.1041 +}
10.1042 +ko.isWriteableObservable = function (instance) {
10.1043 + // Observable
10.1044 + if ((typeof instance == "function") && instance[protoProperty] === ko.observable)
10.1045 + return true;
10.1046 + // Writeable dependent observable
10.1047 + if ((typeof instance == "function") && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
10.1048 + return true;
10.1049 + // Anything else
10.1050 + return false;
10.1051 +}
10.1052 +
10.1053 +
10.1054 +ko.exportSymbol('observable', ko.observable);
10.1055 +ko.exportSymbol('isObservable', ko.isObservable);
10.1056 +ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
10.1057 +ko.observableArray = function (initialValues) {
10.1058 + if (arguments.length == 0) {
10.1059 + // Zero-parameter constructor initializes to empty array
10.1060 + initialValues = [];
10.1061 + }
10.1062 + if ((initialValues !== null) && (initialValues !== undefined) && !('length' in initialValues))
10.1063 + throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
10.1064 +
10.1065 + var result = ko.observable(initialValues);
10.1066 + ko.utils.extend(result, ko.observableArray['fn']);
10.1067 + return result;
10.1068 +}
10.1069 +
10.1070 +ko.observableArray['fn'] = {
10.1071 + 'remove': function (valueOrPredicate) {
10.1072 + var underlyingArray = this.peek();
10.1073 + var removedValues = [];
10.1074 + var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
10.1075 + for (var i = 0; i < underlyingArray.length; i++) {
10.1076 + var value = underlyingArray[i];
10.1077 + if (predicate(value)) {
10.1078 + if (removedValues.length === 0) {
10.1079 + this.valueWillMutate();
10.1080 + }
10.1081 + removedValues.push(value);
10.1082 + underlyingArray.splice(i, 1);
10.1083 + i--;
10.1084 + }
10.1085 + }
10.1086 + if (removedValues.length) {
10.1087 + this.valueHasMutated();
10.1088 + }
10.1089 + return removedValues;
10.1090 + },
10.1091 +
10.1092 + 'removeAll': function (arrayOfValues) {
10.1093 + // If you passed zero args, we remove everything
10.1094 + if (arrayOfValues === undefined) {
10.1095 + var underlyingArray = this.peek();
10.1096 + var allValues = underlyingArray.slice(0);
10.1097 + this.valueWillMutate();
10.1098 + underlyingArray.splice(0, underlyingArray.length);
10.1099 + this.valueHasMutated();
10.1100 + return allValues;
10.1101 + }
10.1102 + // If you passed an arg, we interpret it as an array of entries to remove
10.1103 + if (!arrayOfValues)
10.1104 + return [];
10.1105 + return this['remove'](function (value) {
10.1106 + return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
10.1107 + });
10.1108 + },
10.1109 +
10.1110 + 'destroy': function (valueOrPredicate) {
10.1111 + var underlyingArray = this.peek();
10.1112 + var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
10.1113 + this.valueWillMutate();
10.1114 + for (var i = underlyingArray.length - 1; i >= 0; i--) {
10.1115 + var value = underlyingArray[i];
10.1116 + if (predicate(value))
10.1117 + underlyingArray[i]["_destroy"] = true;
10.1118 + }
10.1119 + this.valueHasMutated();
10.1120 + },
10.1121 +
10.1122 + 'destroyAll': function (arrayOfValues) {
10.1123 + // If you passed zero args, we destroy everything
10.1124 + if (arrayOfValues === undefined)
10.1125 + return this['destroy'](function() { return true });
10.1126 +
10.1127 + // If you passed an arg, we interpret it as an array of entries to destroy
10.1128 + if (!arrayOfValues)
10.1129 + return [];
10.1130 + return this['destroy'](function (value) {
10.1131 + return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
10.1132 + });
10.1133 + },
10.1134 +
10.1135 + 'indexOf': function (item) {
10.1136 + var underlyingArray = this();
10.1137 + return ko.utils.arrayIndexOf(underlyingArray, item);
10.1138 + },
10.1139 +
10.1140 + 'replace': function(oldItem, newItem) {
10.1141 + var index = this['indexOf'](oldItem);
10.1142 + if (index >= 0) {
10.1143 + this.valueWillMutate();
10.1144 + this.peek()[index] = newItem;
10.1145 + this.valueHasMutated();
10.1146 + }
10.1147 + }
10.1148 +}
10.1149 +
10.1150 +// Populate ko.observableArray.fn with read/write functions from native arrays
10.1151 +// Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
10.1152 +// because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale
10.1153 +ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
10.1154 + ko.observableArray['fn'][methodName] = function () {
10.1155 + // Use "peek" to avoid creating a subscription in any computed that we're executing in the context of
10.1156 + // (for consistency with mutating regular observables)
10.1157 + var underlyingArray = this.peek();
10.1158 + this.valueWillMutate();
10.1159 + var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
10.1160 + this.valueHasMutated();
10.1161 + return methodCallResult;
10.1162 + };
10.1163 +});
10.1164 +
10.1165 +// Populate ko.observableArray.fn with read-only functions from native arrays
10.1166 +ko.utils.arrayForEach(["slice"], function (methodName) {
10.1167 + ko.observableArray['fn'][methodName] = function () {
10.1168 + var underlyingArray = this();
10.1169 + return underlyingArray[methodName].apply(underlyingArray, arguments);
10.1170 + };
10.1171 +});
10.1172 +
10.1173 +ko.exportSymbol('observableArray', ko.observableArray);
10.1174 +ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
10.1175 + var _latestValue,
10.1176 + _hasBeenEvaluated = false,
10.1177 + _isBeingEvaluated = false,
10.1178 + readFunction = evaluatorFunctionOrOptions;
10.1179 +
10.1180 + if (readFunction && typeof readFunction == "object") {
10.1181 + // Single-parameter syntax - everything is on this "options" param
10.1182 + options = readFunction;
10.1183 + readFunction = options["read"];
10.1184 + } else {
10.1185 + // Multi-parameter syntax - construct the options according to the params passed
10.1186 + options = options || {};
10.1187 + if (!readFunction)
10.1188 + readFunction = options["read"];
10.1189 + }
10.1190 + if (typeof readFunction != "function")
10.1191 + throw new Error("Pass a function that returns the value of the ko.computed");
10.1192 +
10.1193 + function addSubscriptionToDependency(subscribable) {
10.1194 + _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync));
10.1195 + }
10.1196 +
10.1197 + function disposeAllSubscriptionsToDependencies() {
10.1198 + ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) {
10.1199 + subscription.dispose();
10.1200 + });
10.1201 + _subscriptionsToDependencies = [];
10.1202 + }
10.1203 +
10.1204 + function evaluatePossiblyAsync() {
10.1205 + var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
10.1206 + if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
10.1207 + clearTimeout(evaluationTimeoutInstance);
10.1208 + evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
10.1209 + } else
10.1210 + evaluateImmediate();
10.1211 + }
10.1212 +
10.1213 + function evaluateImmediate() {
10.1214 + if (_isBeingEvaluated) {
10.1215 + // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
10.1216 + // This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
10.1217 + // certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
10.1218 + // their own re-evaluation. Further discussion at https://github.com/SteveSanderson/knockout/pull/387
10.1219 + return;
10.1220 + }
10.1221 +
10.1222 + // Don't dispose on first evaluation, because the "disposeWhen" callback might
10.1223 + // e.g., dispose when the associated DOM element isn't in the doc, and it's not
10.1224 + // going to be in the doc until *after* the first evaluation
10.1225 + if (_hasBeenEvaluated && disposeWhen()) {
10.1226 + dispose();
10.1227 + return;
10.1228 + }
10.1229 +
10.1230 + _isBeingEvaluated = true;
10.1231 + try {
10.1232 + // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
10.1233 + // Then, during evaluation, we cross off any that are in fact still being used.
10.1234 + var disposalCandidates = ko.utils.arrayMap(_subscriptionsToDependencies, function(item) {return item.target;});
10.1235 +
10.1236 + ko.dependencyDetection.begin(function(subscribable) {
10.1237 + var inOld;
10.1238 + if ((inOld = ko.utils.arrayIndexOf(disposalCandidates, subscribable)) >= 0)
10.1239 + disposalCandidates[inOld] = undefined; // Don't want to dispose this subscription, as it's still being used
10.1240 + else
10.1241 + addSubscriptionToDependency(subscribable); // Brand new subscription - add it
10.1242 + });
10.1243 +
10.1244 + var newValue = readFunction.call(evaluatorFunctionTarget);
10.1245 +
10.1246 + // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
10.1247 + for (var i = disposalCandidates.length - 1; i >= 0; i--) {
10.1248 + if (disposalCandidates[i])
10.1249 + _subscriptionsToDependencies.splice(i, 1)[0].dispose();
10.1250 + }
10.1251 + _hasBeenEvaluated = true;
10.1252 +
10.1253 + dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
10.1254 + _latestValue = newValue;
10.1255 + if (DEBUG) dependentObservable._latestValue = _latestValue;
10.1256 + } finally {
10.1257 + ko.dependencyDetection.end();
10.1258 + }
10.1259 +
10.1260 + dependentObservable["notifySubscribers"](_latestValue);
10.1261 + _isBeingEvaluated = false;
10.1262 + if (!_subscriptionsToDependencies.length)
10.1263 + dispose();
10.1264 + }
10.1265 +
10.1266 + function dependentObservable() {
10.1267 + if (arguments.length > 0) {
10.1268 + if (typeof writeFunction === "function") {
10.1269 + // Writing a value
10.1270 + writeFunction.apply(evaluatorFunctionTarget, arguments);
10.1271 + } else {
10.1272 + throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
10.1273 + }
10.1274 + return this; // Permits chained assignments
10.1275 + } else {
10.1276 + // Reading the value
10.1277 + if (!_hasBeenEvaluated)
10.1278 + evaluateImmediate();
10.1279 + ko.dependencyDetection.registerDependency(dependentObservable);
10.1280 + return _latestValue;
10.1281 + }
10.1282 + }
10.1283 +
10.1284 + function peek() {
10.1285 + if (!_hasBeenEvaluated)
10.1286 + evaluateImmediate();
10.1287 + return _latestValue;
10.1288 + }
10.1289 +
10.1290 + function isActive() {
10.1291 + return !_hasBeenEvaluated || _subscriptionsToDependencies.length > 0;
10.1292 + }
10.1293 +
10.1294 + // By here, "options" is always non-null
10.1295 + var writeFunction = options["write"],
10.1296 + disposeWhenNodeIsRemoved = options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
10.1297 + disposeWhen = options["disposeWhen"] || options.disposeWhen || function() { return false; },
10.1298 + dispose = disposeAllSubscriptionsToDependencies,
10.1299 + _subscriptionsToDependencies = [],
10.1300 + evaluationTimeoutInstance = null;
10.1301 +
10.1302 + if (!evaluatorFunctionTarget)
10.1303 + evaluatorFunctionTarget = options["owner"];
10.1304 +
10.1305 + dependentObservable.peek = peek;
10.1306 + dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; };
10.1307 + dependentObservable.hasWriteFunction = typeof options["write"] === "function";
10.1308 + dependentObservable.dispose = function () { dispose(); };
10.1309 + dependentObservable.isActive = isActive;
10.1310 + dependentObservable.valueHasMutated = function() {
10.1311 + _hasBeenEvaluated = false;
10.1312 + evaluateImmediate();
10.1313 + };
10.1314 +
10.1315 + ko.subscribable.call(dependentObservable);
10.1316 + ko.utils.extend(dependentObservable, ko.dependentObservable['fn']);
10.1317 +
10.1318 + ko.exportProperty(dependentObservable, 'peek', dependentObservable.peek);
10.1319 + ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
10.1320 + ko.exportProperty(dependentObservable, 'isActive', dependentObservable.isActive);
10.1321 + ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount);
10.1322 +
10.1323 + // Evaluate, unless deferEvaluation is true
10.1324 + if (options['deferEvaluation'] !== true)
10.1325 + evaluateImmediate();
10.1326 +
10.1327 + // Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values.
10.1328 + // But skip if isActive is false (there will never be any dependencies to dispose).
10.1329 + // (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(),
10.1330 + // plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
10.1331 + if (disposeWhenNodeIsRemoved && isActive()) {
10.1332 + dispose = function() {
10.1333 + ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, arguments.callee);
10.1334 + disposeAllSubscriptionsToDependencies();
10.1335 + };
10.1336 + ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
10.1337 + var existingDisposeWhenFunction = disposeWhen;
10.1338 + disposeWhen = function () {
10.1339 + return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || existingDisposeWhenFunction();
10.1340 + }
10.1341 + }
10.1342 +
10.1343 + return dependentObservable;
10.1344 +};
10.1345 +
10.1346 +ko.isComputed = function(instance) {
10.1347 + return ko.hasPrototype(instance, ko.dependentObservable);
10.1348 +};
10.1349 +
10.1350 +var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
10.1351 +ko.dependentObservable[protoProp] = ko.observable;
10.1352 +
10.1353 +ko.dependentObservable['fn'] = {};
10.1354 +ko.dependentObservable['fn'][protoProp] = ko.dependentObservable;
10.1355 +
10.1356 +ko.exportSymbol('dependentObservable', ko.dependentObservable);
10.1357 +ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
10.1358 +ko.exportSymbol('isComputed', ko.isComputed);
10.1359 +
10.1360 +(function() {
10.1361 + var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
10.1362 +
10.1363 + ko.toJS = function(rootObject) {
10.1364 + if (arguments.length == 0)
10.1365 + throw new Error("When calling ko.toJS, pass the object you want to convert.");
10.1366 +
10.1367 + // We just unwrap everything at every level in the object graph
10.1368 + return mapJsObjectGraph(rootObject, function(valueToMap) {
10.1369 + // Loop because an observable's value might in turn be another observable wrapper
10.1370 + for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++)
10.1371 + valueToMap = valueToMap();
10.1372 + return valueToMap;
10.1373 + });
10.1374 + };
10.1375 +
10.1376 + ko.toJSON = function(rootObject, replacer, space) { // replacer and space are optional
10.1377 + var plainJavaScriptObject = ko.toJS(rootObject);
10.1378 + return ko.utils.stringifyJson(plainJavaScriptObject, replacer, space);
10.1379 + };
10.1380 +
10.1381 + function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
10.1382 + visitedObjects = visitedObjects || new objectLookup();
10.1383 +
10.1384 + rootObject = mapInputCallback(rootObject);
10.1385 + var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date));
10.1386 + if (!canHaveProperties)
10.1387 + return rootObject;
10.1388 +
10.1389 + var outputProperties = rootObject instanceof Array ? [] : {};
10.1390 + visitedObjects.save(rootObject, outputProperties);
10.1391 +
10.1392 + visitPropertiesOrArrayEntries(rootObject, function(indexer) {
10.1393 + var propertyValue = mapInputCallback(rootObject[indexer]);
10.1394 +
10.1395 + switch (typeof propertyValue) {
10.1396 + case "boolean":
10.1397 + case "number":
10.1398 + case "string":
10.1399 + case "function":
10.1400 + outputProperties[indexer] = propertyValue;
10.1401 + break;
10.1402 + case "object":
10.1403 + case "undefined":
10.1404 + var previouslyMappedValue = visitedObjects.get(propertyValue);
10.1405 + outputProperties[indexer] = (previouslyMappedValue !== undefined)
10.1406 + ? previouslyMappedValue
10.1407 + : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects);
10.1408 + break;
10.1409 + }
10.1410 + });
10.1411 +
10.1412 + return outputProperties;
10.1413 + }
10.1414 +
10.1415 + function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
10.1416 + if (rootObject instanceof Array) {
10.1417 + for (var i = 0; i < rootObject.length; i++)
10.1418 + visitorCallback(i);
10.1419 +
10.1420 + // For arrays, also respect toJSON property for custom mappings (fixes #278)
10.1421 + if (typeof rootObject['toJSON'] == 'function')
10.1422 + visitorCallback('toJSON');
10.1423 + } else {
10.1424 + for (var propertyName in rootObject)
10.1425 + visitorCallback(propertyName);
10.1426 + }
10.1427 + };
10.1428 +
10.1429 + function objectLookup() {
10.1430 + var keys = [];
10.1431 + var values = [];
10.1432 + this.save = function(key, value) {
10.1433 + var existingIndex = ko.utils.arrayIndexOf(keys, key);
10.1434 + if (existingIndex >= 0)
10.1435 + values[existingIndex] = value;
10.1436 + else {
10.1437 + keys.push(key);
10.1438 + values.push(value);
10.1439 + }
10.1440 + };
10.1441 + this.get = function(key) {
10.1442 + var existingIndex = ko.utils.arrayIndexOf(keys, key);
10.1443 + return (existingIndex >= 0) ? values[existingIndex] : undefined;
10.1444 + };
10.1445 + };
10.1446 +})();
10.1447 +
10.1448 +ko.exportSymbol('toJS', ko.toJS);
10.1449 +ko.exportSymbol('toJSON', ko.toJSON);
10.1450 +(function () {
10.1451 + var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
10.1452 +
10.1453 + // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
10.1454 + // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
10.1455 + // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
10.1456 + ko.selectExtensions = {
10.1457 + readValue : function(element) {
10.1458 + switch (ko.utils.tagNameLower(element)) {
10.1459 + case 'option':
10.1460 + if (element[hasDomDataExpandoProperty] === true)
10.1461 + return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
10.1462 + return ko.utils.ieVersion <= 7
10.1463 + ? (element.getAttributeNode('value').specified ? element.value : element.text)
10.1464 + : element.value;
10.1465 + case 'select':
10.1466 + return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
10.1467 + default:
10.1468 + return element.value;
10.1469 + }
10.1470 + },
10.1471 +
10.1472 + writeValue: function(element, value) {
10.1473 + switch (ko.utils.tagNameLower(element)) {
10.1474 + case 'option':
10.1475 + switch(typeof value) {
10.1476 + case "string":
10.1477 + ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
10.1478 + if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
10.1479 + delete element[hasDomDataExpandoProperty];
10.1480 + }
10.1481 + element.value = value;
10.1482 + break;
10.1483 + default:
10.1484 + // Store arbitrary object using DomData
10.1485 + ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
10.1486 + element[hasDomDataExpandoProperty] = true;
10.1487 +
10.1488 + // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
10.1489 + element.value = typeof value === "number" ? value : "";
10.1490 + break;
10.1491 + }
10.1492 + break;
10.1493 + case 'select':
10.1494 + for (var i = element.options.length - 1; i >= 0; i--) {
10.1495 + if (ko.selectExtensions.readValue(element.options[i]) == value) {
10.1496 + element.selectedIndex = i;
10.1497 + break;
10.1498 + }
10.1499 + }
10.1500 + break;
10.1501 + default:
10.1502 + if ((value === null) || (value === undefined))
10.1503 + value = "";
10.1504 + element.value = value;
10.1505 + break;
10.1506 + }
10.1507 + }
10.1508 + };
10.1509 +})();
10.1510 +
10.1511 +ko.exportSymbol('selectExtensions', ko.selectExtensions);
10.1512 +ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
10.1513 +ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
10.1514 +ko.expressionRewriting = (function () {
10.1515 + var restoreCapturedTokensRegex = /\@ko_token_(\d+)\@/g;
10.1516 + var javaScriptReservedWords = ["true", "false"];
10.1517 +
10.1518 + // Matches something that can be assigned to--either an isolated identifier or something ending with a property accessor
10.1519 + // This is designed to be simple and avoid false negatives, but could produce false positives (e.g., a+b.c).
10.1520 + var javaScriptAssignmentTarget = /^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i;
10.1521 +
10.1522 + function restoreTokens(string, tokens) {
10.1523 + var prevValue = null;
10.1524 + while (string != prevValue) { // Keep restoring tokens until it no longer makes a difference (they may be nested)
10.1525 + prevValue = string;
10.1526 + string = string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) {
10.1527 + return tokens[tokenIndex];
10.1528 + });
10.1529 + }
10.1530 + return string;
10.1531 + }
10.1532 +
10.1533 + function getWriteableValue(expression) {
10.1534 + if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0)
10.1535 + return false;
10.1536 + var match = expression.match(javaScriptAssignmentTarget);
10.1537 + return match === null ? false : match[1] ? ('Object(' + match[1] + ')' + match[2]) : expression;
10.1538 + }
10.1539 +
10.1540 + function ensureQuoted(key) {
10.1541 + var trimmedKey = ko.utils.stringTrim(key);
10.1542 + switch (trimmedKey.length && trimmedKey.charAt(0)) {
10.1543 + case "'":
10.1544 + case '"':
10.1545 + return key;
10.1546 + default:
10.1547 + return "'" + trimmedKey + "'";
10.1548 + }
10.1549 + }
10.1550 +
10.1551 + return {
10.1552 + bindingRewriteValidators: [],
10.1553 +
10.1554 + parseObjectLiteral: function(objectLiteralString) {
10.1555 + // A full tokeniser+lexer would add too much weight to this library, so here's a simple parser
10.1556 + // that is sufficient just to split an object literal string into a set of top-level key-value pairs
10.1557 +
10.1558 + var str = ko.utils.stringTrim(objectLiteralString);
10.1559 + if (str.length < 3)
10.1560 + return [];
10.1561 + if (str.charAt(0) === "{")// Ignore any braces surrounding the whole object literal
10.1562 + str = str.substring(1, str.length - 1);
10.1563 +
10.1564 + // Pull out any string literals and regex literals
10.1565 + var tokens = [];
10.1566 + var tokenStart = null, tokenEndChar;
10.1567 + for (var position = 0; position < str.length; position++) {
10.1568 + var c = str.charAt(position);
10.1569 + if (tokenStart === null) {
10.1570 + switch (c) {
10.1571 + case '"':
10.1572 + case "'":
10.1573 + case "/":
10.1574 + tokenStart = position;
10.1575 + tokenEndChar = c;
10.1576 + break;
10.1577 + }
10.1578 + } else if ((c == tokenEndChar) && (str.charAt(position - 1) !== "\\")) {
10.1579 + var token = str.substring(tokenStart, position + 1);
10.1580 + tokens.push(token);
10.1581 + var replacement = "@ko_token_" + (tokens.length - 1) + "@";
10.1582 + str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
10.1583 + position -= (token.length - replacement.length);
10.1584 + tokenStart = null;
10.1585 + }
10.1586 + }
10.1587 +
10.1588 + // Next pull out balanced paren, brace, and bracket blocks
10.1589 + tokenStart = null;
10.1590 + tokenEndChar = null;
10.1591 + var tokenDepth = 0, tokenStartChar = null;
10.1592 + for (var position = 0; position < str.length; position++) {
10.1593 + var c = str.charAt(position);
10.1594 + if (tokenStart === null) {
10.1595 + switch (c) {
10.1596 + case "{": tokenStart = position; tokenStartChar = c;
10.1597 + tokenEndChar = "}";
10.1598 + break;
10.1599 + case "(": tokenStart = position; tokenStartChar = c;
10.1600 + tokenEndChar = ")";
10.1601 + break;
10.1602 + case "[": tokenStart = position; tokenStartChar = c;
10.1603 + tokenEndChar = "]";
10.1604 + break;
10.1605 + }
10.1606 + }
10.1607 +
10.1608 + if (c === tokenStartChar)
10.1609 + tokenDepth++;
10.1610 + else if (c === tokenEndChar) {
10.1611 + tokenDepth--;
10.1612 + if (tokenDepth === 0) {
10.1613 + var token = str.substring(tokenStart, position + 1);
10.1614 + tokens.push(token);
10.1615 + var replacement = "@ko_token_" + (tokens.length - 1) + "@";
10.1616 + str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
10.1617 + position -= (token.length - replacement.length);
10.1618 + tokenStart = null;
10.1619 + }
10.1620 + }
10.1621 + }
10.1622 +
10.1623 + // Now we can safely split on commas to get the key/value pairs
10.1624 + var result = [];
10.1625 + var keyValuePairs = str.split(",");
10.1626 + for (var i = 0, j = keyValuePairs.length; i < j; i++) {
10.1627 + var pair = keyValuePairs[i];
10.1628 + var colonPos = pair.indexOf(":");
10.1629 + if ((colonPos > 0) && (colonPos < pair.length - 1)) {
10.1630 + var key = pair.substring(0, colonPos);
10.1631 + var value = pair.substring(colonPos + 1);
10.1632 + result.push({ 'key': restoreTokens(key, tokens), 'value': restoreTokens(value, tokens) });
10.1633 + } else {
10.1634 + result.push({ 'unknown': restoreTokens(pair, tokens) });
10.1635 + }
10.1636 + }
10.1637 + return result;
10.1638 + },
10.1639 +
10.1640 + preProcessBindings: function (objectLiteralStringOrKeyValueArray) {
10.1641 + var keyValueArray = typeof objectLiteralStringOrKeyValueArray === "string"
10.1642 + ? ko.expressionRewriting.parseObjectLiteral(objectLiteralStringOrKeyValueArray)
10.1643 + : objectLiteralStringOrKeyValueArray;
10.1644 + var resultStrings = [], propertyAccessorResultStrings = [];
10.1645 +
10.1646 + var keyValueEntry;
10.1647 + for (var i = 0; keyValueEntry = keyValueArray[i]; i++) {
10.1648 + if (resultStrings.length > 0)
10.1649 + resultStrings.push(",");
10.1650 +
10.1651 + if (keyValueEntry['key']) {
10.1652 + var quotedKey = ensureQuoted(keyValueEntry['key']), val = keyValueEntry['value'];
10.1653 + resultStrings.push(quotedKey);
10.1654 + resultStrings.push(":");
10.1655 + resultStrings.push(val);
10.1656 +
10.1657 + if (val = getWriteableValue(ko.utils.stringTrim(val))) {
10.1658 + if (propertyAccessorResultStrings.length > 0)
10.1659 + propertyAccessorResultStrings.push(", ");
10.1660 + propertyAccessorResultStrings.push(quotedKey + " : function(__ko_value) { " + val + " = __ko_value; }");
10.1661 + }
10.1662 + } else if (keyValueEntry['unknown']) {
10.1663 + resultStrings.push(keyValueEntry['unknown']);
10.1664 + }
10.1665 + }
10.1666 +
10.1667 + var combinedResult = resultStrings.join("");
10.1668 + if (propertyAccessorResultStrings.length > 0) {
10.1669 + var allPropertyAccessors = propertyAccessorResultStrings.join("");
10.1670 + combinedResult = combinedResult + ", '_ko_property_writers' : { " + allPropertyAccessors + " } ";
10.1671 + }
10.1672 +
10.1673 + return combinedResult;
10.1674 + },
10.1675 +
10.1676 + keyValueArrayContainsKey: function(keyValueArray, key) {
10.1677 + for (var i = 0; i < keyValueArray.length; i++)
10.1678 + if (ko.utils.stringTrim(keyValueArray[i]['key']) == key)
10.1679 + return true;
10.1680 + return false;
10.1681 + },
10.1682 +
10.1683 + // Internal, private KO utility for updating model properties from within bindings
10.1684 + // property: If the property being updated is (or might be) an observable, pass it here
10.1685 + // If it turns out to be a writable observable, it will be written to directly
10.1686 + // allBindingsAccessor: All bindings in the current execution context.
10.1687 + // This will be searched for a '_ko_property_writers' property in case you're writing to a non-observable
10.1688 + // key: The key identifying the property to be written. Example: for { hasFocus: myValue }, write to 'myValue' by specifying the key 'hasFocus'
10.1689 + // value: The value to be written
10.1690 + // checkIfDifferent: If true, and if the property being written is a writable observable, the value will only be written if
10.1691 + // it is !== existing value on that writable observable
10.1692 + writeValueToProperty: function(property, allBindingsAccessor, key, value, checkIfDifferent) {
10.1693 + if (!property || !ko.isWriteableObservable(property)) {
10.1694 + var propWriters = allBindingsAccessor()['_ko_property_writers'];
10.1695 + if (propWriters && propWriters[key])
10.1696 + propWriters[key](value);
10.1697 + } else if (!checkIfDifferent || property.peek() !== value) {
10.1698 + property(value);
10.1699 + }
10.1700 + }
10.1701 + };
10.1702 +})();
10.1703 +
10.1704 +ko.exportSymbol('expressionRewriting', ko.expressionRewriting);
10.1705 +ko.exportSymbol('expressionRewriting.bindingRewriteValidators', ko.expressionRewriting.bindingRewriteValidators);
10.1706 +ko.exportSymbol('expressionRewriting.parseObjectLiteral', ko.expressionRewriting.parseObjectLiteral);
10.1707 +ko.exportSymbol('expressionRewriting.preProcessBindings', ko.expressionRewriting.preProcessBindings);
10.1708 +
10.1709 +// For backward compatibility, define the following aliases. (Previously, these function names were misleading because
10.1710 +// they referred to JSON specifically, even though they actually work with arbitrary JavaScript object literal expressions.)
10.1711 +ko.exportSymbol('jsonExpressionRewriting', ko.expressionRewriting);
10.1712 +ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.expressionRewriting.preProcessBindings);(function() {
10.1713 + // "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
10.1714 + // may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
10.1715 + // If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
10.1716 + // of that virtual hierarchy
10.1717 + //
10.1718 + // The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
10.1719 + // without having to scatter special cases all over the binding and templating code.
10.1720 +
10.1721 + // IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
10.1722 + // but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
10.1723 + // So, use node.text where available, and node.nodeValue elsewhere
10.1724 + var commentNodesHaveTextProperty = document.createComment("test").text === "<!--test-->";
10.1725 +
10.1726 + var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*-->$/ : /^\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*$/;
10.1727 + var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
10.1728 + var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
10.1729 +
10.1730 + function isStartComment(node) {
10.1731 + return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
10.1732 + }
10.1733 +
10.1734 + function isEndComment(node) {
10.1735 + return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(endCommentRegex);
10.1736 + }
10.1737 +
10.1738 + function getVirtualChildren(startComment, allowUnbalanced) {
10.1739 + var currentNode = startComment;
10.1740 + var depth = 1;
10.1741 + var children = [];
10.1742 + while (currentNode = currentNode.nextSibling) {
10.1743 + if (isEndComment(currentNode)) {
10.1744 + depth--;
10.1745 + if (depth === 0)
10.1746 + return children;
10.1747 + }
10.1748 +
10.1749 + children.push(currentNode);
10.1750 +
10.1751 + if (isStartComment(currentNode))
10.1752 + depth++;
10.1753 + }
10.1754 + if (!allowUnbalanced)
10.1755 + throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
10.1756 + return null;
10.1757 + }
10.1758 +
10.1759 + function getMatchingEndComment(startComment, allowUnbalanced) {
10.1760 + var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
10.1761 + if (allVirtualChildren) {
10.1762 + if (allVirtualChildren.length > 0)
10.1763 + return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
10.1764 + return startComment.nextSibling;
10.1765 + } else
10.1766 + return null; // Must have no matching end comment, and allowUnbalanced is true
10.1767 + }
10.1768 +
10.1769 + function getUnbalancedChildTags(node) {
10.1770 + // e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span>
10.1771 + // from <div>OK</div><!-- /ko --><!-- /ko -->, returns: <!-- /ko --><!-- /ko -->
10.1772 + var childNode = node.firstChild, captureRemaining = null;
10.1773 + if (childNode) {
10.1774 + do {
10.1775 + if (captureRemaining) // We already hit an unbalanced node and are now just scooping up all subsequent nodes
10.1776 + captureRemaining.push(childNode);
10.1777 + else if (isStartComment(childNode)) {
10.1778 + var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true);
10.1779 + if (matchingEndComment) // It's a balanced tag, so skip immediately to the end of this virtual set
10.1780 + childNode = matchingEndComment;
10.1781 + else
10.1782 + captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point
10.1783 + } else if (isEndComment(childNode)) {
10.1784 + captureRemaining = [childNode]; // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing
10.1785 + }
10.1786 + } while (childNode = childNode.nextSibling);
10.1787 + }
10.1788 + return captureRemaining;
10.1789 + }
10.1790 +
10.1791 + ko.virtualElements = {
10.1792 + allowedBindings: {},
10.1793 +
10.1794 + childNodes: function(node) {
10.1795 + return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
10.1796 + },
10.1797 +
10.1798 + emptyNode: function(node) {
10.1799 + if (!isStartComment(node))
10.1800 + ko.utils.emptyDomNode(node);
10.1801 + else {
10.1802 + var virtualChildren = ko.virtualElements.childNodes(node);
10.1803 + for (var i = 0, j = virtualChildren.length; i < j; i++)
10.1804 + ko.removeNode(virtualChildren[i]);
10.1805 + }
10.1806 + },
10.1807 +
10.1808 + setDomNodeChildren: function(node, childNodes) {
10.1809 + if (!isStartComment(node))
10.1810 + ko.utils.setDomNodeChildren(node, childNodes);
10.1811 + else {
10.1812 + ko.virtualElements.emptyNode(node);
10.1813 + var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
10.1814 + for (var i = 0, j = childNodes.length; i < j; i++)
10.1815 + endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
10.1816 + }
10.1817 + },
10.1818 +
10.1819 + prepend: function(containerNode, nodeToPrepend) {
10.1820 + if (!isStartComment(containerNode)) {
10.1821 + if (containerNode.firstChild)
10.1822 + containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
10.1823 + else
10.1824 + containerNode.appendChild(nodeToPrepend);
10.1825 + } else {
10.1826 + // Start comments must always have a parent and at least one following sibling (the end comment)
10.1827 + containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
10.1828 + }
10.1829 + },
10.1830 +
10.1831 + insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
10.1832 + if (!insertAfterNode) {
10.1833 + ko.virtualElements.prepend(containerNode, nodeToInsert);
10.1834 + } else if (!isStartComment(containerNode)) {
10.1835 + // Insert after insertion point
10.1836 + if (insertAfterNode.nextSibling)
10.1837 + containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
10.1838 + else
10.1839 + containerNode.appendChild(nodeToInsert);
10.1840 + } else {
10.1841 + // Children of start comments must always have a parent and at least one following sibling (the end comment)
10.1842 + containerNode.parentNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
10.1843 + }
10.1844 + },
10.1845 +
10.1846 + firstChild: function(node) {
10.1847 + if (!isStartComment(node))
10.1848 + return node.firstChild;
10.1849 + if (!node.nextSibling || isEndComment(node.nextSibling))
10.1850 + return null;
10.1851 + return node.nextSibling;
10.1852 + },
10.1853 +
10.1854 + nextSibling: function(node) {
10.1855 + if (isStartComment(node))
10.1856 + node = getMatchingEndComment(node);
10.1857 + if (node.nextSibling && isEndComment(node.nextSibling))
10.1858 + return null;
10.1859 + return node.nextSibling;
10.1860 + },
10.1861 +
10.1862 + virtualNodeBindingValue: function(node) {
10.1863 + var regexMatch = isStartComment(node);
10.1864 + return regexMatch ? regexMatch[1] : null;
10.1865 + },
10.1866 +
10.1867 + normaliseVirtualElementDomStructure: function(elementVerified) {
10.1868 + // Workaround for https://github.com/SteveSanderson/knockout/issues/155
10.1869 + // (IE <= 8 or IE 9 quirks mode parses your HTML weirdly, treating closing </li> tags as if they don't exist, thereby moving comment nodes
10.1870 + // that are direct descendants of <ul> into the preceding <li>)
10.1871 + if (!htmlTagsWithOptionallyClosingChildren[ko.utils.tagNameLower(elementVerified)])
10.1872 + return;
10.1873 +
10.1874 + // Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags
10.1875 + // must be intended to appear *after* that child, so move them there.
10.1876 + var childNode = elementVerified.firstChild;
10.1877 + if (childNode) {
10.1878 + do {
10.1879 + if (childNode.nodeType === 1) {
10.1880 + var unbalancedTags = getUnbalancedChildTags(childNode);
10.1881 + if (unbalancedTags) {
10.1882 + // Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child
10.1883 + var nodeToInsertBefore = childNode.nextSibling;
10.1884 + for (var i = 0; i < unbalancedTags.length; i++) {
10.1885 + if (nodeToInsertBefore)
10.1886 + elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore);
10.1887 + else
10.1888 + elementVerified.appendChild(unbalancedTags[i]);
10.1889 + }
10.1890 + }
10.1891 + }
10.1892 + } while (childNode = childNode.nextSibling);
10.1893 + }
10.1894 + }
10.1895 + };
10.1896 +})();
10.1897 +ko.exportSymbol('virtualElements', ko.virtualElements);
10.1898 +ko.exportSymbol('virtualElements.allowedBindings', ko.virtualElements.allowedBindings);
10.1899 +ko.exportSymbol('virtualElements.emptyNode', ko.virtualElements.emptyNode);
10.1900 +//ko.exportSymbol('virtualElements.firstChild', ko.virtualElements.firstChild); // firstChild is not minified
10.1901 +ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter);
10.1902 +//ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling); // nextSibling is not minified
10.1903 +ko.exportSymbol('virtualElements.prepend', ko.virtualElements.prepend);
10.1904 +ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomNodeChildren);
10.1905 +(function() {
10.1906 + var defaultBindingAttributeName = "data-bind";
10.1907 +
10.1908 + ko.bindingProvider = function() {
10.1909 + this.bindingCache = {};
10.1910 + };
10.1911 +
10.1912 + ko.utils.extend(ko.bindingProvider.prototype, {
10.1913 + 'nodeHasBindings': function(node) {
10.1914 + switch (node.nodeType) {
10.1915 + case 1: return node.getAttribute(defaultBindingAttributeName) != null; // Element
10.1916 + case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node
10.1917 + default: return false;
10.1918 + }
10.1919 + },
10.1920 +
10.1921 + 'getBindings': function(node, bindingContext) {
10.1922 + var bindingsString = this['getBindingsString'](node, bindingContext);
10.1923 + return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
10.1924 + },
10.1925 +
10.1926 + // The following function is only used internally by this default provider.
10.1927 + // It's not part of the interface definition for a general binding provider.
10.1928 + 'getBindingsString': function(node, bindingContext) {
10.1929 + switch (node.nodeType) {
10.1930 + case 1: return node.getAttribute(defaultBindingAttributeName); // Element
10.1931 + case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
10.1932 + default: return null;
10.1933 + }
10.1934 + },
10.1935 +
10.1936 + // The following function is only used internally by this default provider.
10.1937 + // It's not part of the interface definition for a general binding provider.
10.1938 + 'parseBindingsString': function(bindingsString, bindingContext, node) {
10.1939 + try {
10.1940 + var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache);
10.1941 + return bindingFunction(bindingContext, node);
10.1942 + } catch (ex) {
10.1943 + throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
10.1944 + }
10.1945 + }
10.1946 + });
10.1947 +
10.1948 + ko.bindingProvider['instance'] = new ko.bindingProvider();
10.1949 +
10.1950 + function createBindingsStringEvaluatorViaCache(bindingsString, cache) {
10.1951 + var cacheKey = bindingsString;
10.1952 + return cache[cacheKey]
10.1953 + || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString));
10.1954 + }
10.1955 +
10.1956 + function createBindingsStringEvaluator(bindingsString) {
10.1957 + // Build the source for a function that evaluates "expression"
10.1958 + // For each scope variable, add an extra level of "with" nesting
10.1959 + // Example result: with(sc1) { with(sc0) { return (expression) } }
10.1960 + var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString),
10.1961 + functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
10.1962 + return new Function("$context", "$element", functionBody);
10.1963 + }
10.1964 +})();
10.1965 +
10.1966 +ko.exportSymbol('bindingProvider', ko.bindingProvider);
10.1967 +(function () {
10.1968 + ko.bindingHandlers = {};
10.1969 +
10.1970 + ko.bindingContext = function(dataItem, parentBindingContext, dataItemAlias) {
10.1971 + if (parentBindingContext) {
10.1972 + ko.utils.extend(this, parentBindingContext); // Inherit $root and any custom properties
10.1973 + this['$parentContext'] = parentBindingContext;
10.1974 + this['$parent'] = parentBindingContext['$data'];
10.1975 + this['$parents'] = (parentBindingContext['$parents'] || []).slice(0);
10.1976 + this['$parents'].unshift(this['$parent']);
10.1977 + } else {
10.1978 + this['$parents'] = [];
10.1979 + this['$root'] = dataItem;
10.1980 + // Export 'ko' in the binding context so it will be available in bindings and templates
10.1981 + // even if 'ko' isn't exported as a global, such as when using an AMD loader.
10.1982 + // See https://github.com/SteveSanderson/knockout/issues/490
10.1983 + this['ko'] = ko;
10.1984 + }
10.1985 + this['$data'] = dataItem;
10.1986 + if (dataItemAlias)
10.1987 + this[dataItemAlias] = dataItem;
10.1988 + }
10.1989 + ko.bindingContext.prototype['createChildContext'] = function (dataItem, dataItemAlias) {
10.1990 + return new ko.bindingContext(dataItem, this, dataItemAlias);
10.1991 + };
10.1992 + ko.bindingContext.prototype['extend'] = function(properties) {
10.1993 + var clone = ko.utils.extend(new ko.bindingContext(), this);
10.1994 + return ko.utils.extend(clone, properties);
10.1995 + };
10.1996 +
10.1997 + function validateThatBindingIsAllowedForVirtualElements(bindingName) {
10.1998 + var validator = ko.virtualElements.allowedBindings[bindingName];
10.1999 + if (!validator)
10.2000 + throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
10.2001 + }
10.2002 +
10.2003 + function applyBindingsToDescendantsInternal (viewModel, elementOrVirtualElement, bindingContextsMayDifferFromDomParentElement) {
10.2004 + var currentChild, nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
10.2005 + while (currentChild = nextInQueue) {
10.2006 + // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
10.2007 + nextInQueue = ko.virtualElements.nextSibling(currentChild);
10.2008 + applyBindingsToNodeAndDescendantsInternal(viewModel, currentChild, bindingContextsMayDifferFromDomParentElement);
10.2009 + }
10.2010 + }
10.2011 +
10.2012 + function applyBindingsToNodeAndDescendantsInternal (viewModel, nodeVerified, bindingContextMayDifferFromDomParentElement) {
10.2013 + var shouldBindDescendants = true;
10.2014 +
10.2015 + // Perf optimisation: Apply bindings only if...
10.2016 + // (1) We need to store the binding context on this node (because it may differ from the DOM parent node's binding context)
10.2017 + // Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those
10.2018 + // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
10.2019 + var isElement = (nodeVerified.nodeType === 1);
10.2020 + if (isElement) // Workaround IE <= 8 HTML parsing weirdness
10.2021 + ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
10.2022 +
10.2023 + var shouldApplyBindings = (isElement && bindingContextMayDifferFromDomParentElement) // Case (1)
10.2024 + || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified); // Case (2)
10.2025 + if (shouldApplyBindings)
10.2026 + shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, viewModel, bindingContextMayDifferFromDomParentElement).shouldBindDescendants;
10.2027 +
10.2028 + if (shouldBindDescendants) {
10.2029 + // We're recursing automatically into (real or virtual) child nodes without changing binding contexts. So,
10.2030 + // * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
10.2031 + // hence bindingContextsMayDifferFromDomParentElement is false
10.2032 + // * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
10.2033 + // skip over any number of intermediate virtual elements, any of which might define a custom binding context,
10.2034 + // hence bindingContextsMayDifferFromDomParentElement is true
10.2035 + applyBindingsToDescendantsInternal(viewModel, nodeVerified, /* bindingContextsMayDifferFromDomParentElement: */ !isElement);
10.2036 + }
10.2037 + }
10.2038 +
10.2039 + function applyBindingsToNodeInternal (node, bindings, viewModelOrBindingContext, bindingContextMayDifferFromDomParentElement) {
10.2040 + // Need to be sure that inits are only run once, and updates never run until all the inits have been run
10.2041 + var initPhase = 0; // 0 = before all inits, 1 = during inits, 2 = after all inits
10.2042 +
10.2043 + // Each time the dependentObservable is evaluated (after data changes),
10.2044 + // the binding attribute is reparsed so that it can pick out the correct
10.2045 + // model properties in the context of the changed data.
10.2046 + // DOM event callbacks need to be able to access this changed data,
10.2047 + // so we need a single parsedBindings variable (shared by all callbacks
10.2048 + // associated with this node's bindings) that all the closures can access.
10.2049 + var parsedBindings;
10.2050 + function makeValueAccessor(bindingKey) {
10.2051 + return function () { return parsedBindings[bindingKey] }
10.2052 + }
10.2053 + function parsedBindingsAccessor() {
10.2054 + return parsedBindings;
10.2055 + }
10.2056 +
10.2057 + var bindingHandlerThatControlsDescendantBindings;
10.2058 + ko.dependentObservable(
10.2059 + function () {
10.2060 + // Ensure we have a nonnull binding context to work with
10.2061 + var bindingContextInstance = viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
10.2062 + ? viewModelOrBindingContext
10.2063 + : new ko.bindingContext(ko.utils.unwrapObservable(viewModelOrBindingContext));
10.2064 + var viewModel = bindingContextInstance['$data'];
10.2065 +
10.2066 + // Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
10.2067 + // we can easily recover it just by scanning up the node's ancestors in the DOM
10.2068 + // (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
10.2069 + if (bindingContextMayDifferFromDomParentElement)
10.2070 + ko.storedBindingContextForNode(node, bindingContextInstance);
10.2071 +
10.2072 + // Use evaluatedBindings if given, otherwise fall back on asking the bindings provider to give us some bindings
10.2073 + var evaluatedBindings = (typeof bindings == "function") ? bindings(bindingContextInstance, node) : bindings;
10.2074 + parsedBindings = evaluatedBindings || ko.bindingProvider['instance']['getBindings'](node, bindingContextInstance);
10.2075 +
10.2076 + if (parsedBindings) {
10.2077 + // First run all the inits, so bindings can register for notification on changes
10.2078 + if (initPhase === 0) {
10.2079 + initPhase = 1;
10.2080 + for (var bindingKey in parsedBindings) {
10.2081 + var binding = ko.bindingHandlers[bindingKey];
10.2082 + if (binding && node.nodeType === 8)
10.2083 + validateThatBindingIsAllowedForVirtualElements(bindingKey);
10.2084 +
10.2085 + if (binding && typeof binding["init"] == "function") {
10.2086 + var handlerInitFn = binding["init"];
10.2087 + var initResult = handlerInitFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
10.2088 +
10.2089 + // If this binding handler claims to control descendant bindings, make a note of this
10.2090 + if (initResult && initResult['controlsDescendantBindings']) {
10.2091 + if (bindingHandlerThatControlsDescendantBindings !== undefined)
10.2092 + throw new Error("Multiple bindings (" + bindingHandlerThatControlsDescendantBindings + " and " + bindingKey + ") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
10.2093 + bindingHandlerThatControlsDescendantBindings = bindingKey;
10.2094 + }
10.2095 + }
10.2096 + }
10.2097 + initPhase = 2;
10.2098 + }
10.2099 +
10.2100 + // ... then run all the updates, which might trigger changes even on the first evaluation
10.2101 + if (initPhase === 2) {
10.2102 + for (var bindingKey in parsedBindings) {
10.2103 + var binding = ko.bindingHandlers[bindingKey];
10.2104 + if (binding && typeof binding["update"] == "function") {
10.2105 + var handlerUpdateFn = binding["update"];
10.2106 + handlerUpdateFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
10.2107 + }
10.2108 + }
10.2109 + }
10.2110 + }
10.2111 + },
10.2112 + null,
10.2113 + { disposeWhenNodeIsRemoved : node }
10.2114 + );
10.2115 +
10.2116 + return {
10.2117 + shouldBindDescendants: bindingHandlerThatControlsDescendantBindings === undefined
10.2118 + };
10.2119 + };
10.2120 +
10.2121 + var storedBindingContextDomDataKey = "__ko_bindingContext__";
10.2122 + ko.storedBindingContextForNode = function (node, bindingContext) {
10.2123 + if (arguments.length == 2)
10.2124 + ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
10.2125 + else
10.2126 + return ko.utils.domData.get(node, storedBindingContextDomDataKey);
10.2127 + }
10.2128 +
10.2129 + ko.applyBindingsToNode = function (node, bindings, viewModel) {
10.2130 + if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
10.2131 + ko.virtualElements.normaliseVirtualElementDomStructure(node);
10.2132 + return applyBindingsToNodeInternal(node, bindings, viewModel, true);
10.2133 + };
10.2134 +
10.2135 + ko.applyBindingsToDescendants = function(viewModel, rootNode) {
10.2136 + if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
10.2137 + applyBindingsToDescendantsInternal(viewModel, rootNode, true);
10.2138 + };
10.2139 +
10.2140 + ko.applyBindings = function (viewModel, rootNode) {
10.2141 + if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
10.2142 + throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
10.2143 + rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
10.2144 +
10.2145 + applyBindingsToNodeAndDescendantsInternal(viewModel, rootNode, true);
10.2146 + };
10.2147 +
10.2148 + // Retrieving binding context from arbitrary nodes
10.2149 + ko.contextFor = function(node) {
10.2150 + // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
10.2151 + switch (node.nodeType) {
10.2152 + case 1:
10.2153 + case 8:
10.2154 + var context = ko.storedBindingContextForNode(node);
10.2155 + if (context) return context;
10.2156 + if (node.parentNode) return ko.contextFor(node.parentNode);
10.2157 + break;
10.2158 + }
10.2159 + return undefined;
10.2160 + };
10.2161 + ko.dataFor = function(node) {
10.2162 + var context = ko.contextFor(node);
10.2163 + return context ? context['$data'] : undefined;
10.2164 + };
10.2165 +
10.2166 + ko.exportSymbol('bindingHandlers', ko.bindingHandlers);
10.2167 + ko.exportSymbol('applyBindings', ko.applyBindings);
10.2168 + ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants);
10.2169 + ko.exportSymbol('applyBindingsToNode', ko.applyBindingsToNode);
10.2170 + ko.exportSymbol('contextFor', ko.contextFor);
10.2171 + ko.exportSymbol('dataFor', ko.dataFor);
10.2172 +})();
10.2173 +var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
10.2174 +ko.bindingHandlers['attr'] = {
10.2175 + 'update': function(element, valueAccessor, allBindingsAccessor) {
10.2176 + var value = ko.utils.unwrapObservable(valueAccessor()) || {};
10.2177 + for (var attrName in value) {
10.2178 + if (typeof attrName == "string") {
10.2179 + var attrValue = ko.utils.unwrapObservable(value[attrName]);
10.2180 +
10.2181 + // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
10.2182 + // when someProp is a "no value"-like value (strictly null, false, or undefined)
10.2183 + // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
10.2184 + var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
10.2185 + if (toRemove)
10.2186 + element.removeAttribute(attrName);
10.2187 +
10.2188 + // In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
10.2189 + // HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
10.2190 + // but instead of figuring out the mode, we'll just set the attribute through the Javascript
10.2191 + // property for IE <= 8.
10.2192 + if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
10.2193 + attrName = attrHtmlToJavascriptMap[attrName];
10.2194 + if (toRemove)
10.2195 + element.removeAttribute(attrName);
10.2196 + else
10.2197 + element[attrName] = attrValue;
10.2198 + } else if (!toRemove) {
10.2199 + element.setAttribute(attrName, attrValue.toString());
10.2200 + }
10.2201 +
10.2202 + // Treat "name" specially - although you can think of it as an attribute, it also needs
10.2203 + // special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
10.2204 + // Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
10.2205 + // entirely, and there's no strong reason to allow for such casing in HTML.
10.2206 + if (attrName === "name") {
10.2207 + ko.utils.setElementName(element, toRemove ? "" : attrValue.toString());
10.2208 + }
10.2209 + }
10.2210 + }
10.2211 + }
10.2212 +};
10.2213 +ko.bindingHandlers['checked'] = {
10.2214 + 'init': function (element, valueAccessor, allBindingsAccessor) {
10.2215 + var updateHandler = function() {
10.2216 + var valueToWrite;
10.2217 + if (element.type == "checkbox") {
10.2218 + valueToWrite = element.checked;
10.2219 + } else if ((element.type == "radio") && (element.checked)) {
10.2220 + valueToWrite = element.value;
10.2221 + } else {
10.2222 + return; // "checked" binding only responds to checkboxes and selected radio buttons
10.2223 + }
10.2224 +
10.2225 + var modelValue = valueAccessor(), unwrappedValue = ko.utils.unwrapObservable(modelValue);
10.2226 + if ((element.type == "checkbox") && (unwrappedValue instanceof Array)) {
10.2227 + // For checkboxes bound to an array, we add/remove the checkbox value to that array
10.2228 + // This works for both observable and non-observable arrays
10.2229 + var existingEntryIndex = ko.utils.arrayIndexOf(unwrappedValue, element.value);
10.2230 + if (element.checked && (existingEntryIndex < 0))
10.2231 + modelValue.push(element.value);
10.2232 + else if ((!element.checked) && (existingEntryIndex >= 0))
10.2233 + modelValue.splice(existingEntryIndex, 1);
10.2234 + } else {
10.2235 + ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
10.2236 + }
10.2237 + };
10.2238 + ko.utils.registerEventHandler(element, "click", updateHandler);
10.2239 +
10.2240 + // IE 6 won't allow radio buttons to be selected unless they have a name
10.2241 + if ((element.type == "radio") && !element.name)
10.2242 + ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
10.2243 + },
10.2244 + 'update': function (element, valueAccessor) {
10.2245 + var value = ko.utils.unwrapObservable(valueAccessor());
10.2246 +
10.2247 + if (element.type == "checkbox") {
10.2248 + if (value instanceof Array) {
10.2249 + // When bound to an array, the checkbox being checked represents its value being present in that array
10.2250 + element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0;
10.2251 + } else {
10.2252 + // When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
10.2253 + element.checked = value;
10.2254 + }
10.2255 + } else if (element.type == "radio") {
10.2256 + element.checked = (element.value == value);
10.2257 + }
10.2258 + }
10.2259 +};
10.2260 +var classesWrittenByBindingKey = '__ko__cssValue';
10.2261 +ko.bindingHandlers['css'] = {
10.2262 + 'update': function (element, valueAccessor) {
10.2263 + var value = ko.utils.unwrapObservable(valueAccessor());
10.2264 + if (typeof value == "object") {
10.2265 + for (var className in value) {
10.2266 + var shouldHaveClass = ko.utils.unwrapObservable(value[className]);
10.2267 + ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
10.2268 + }
10.2269 + } else {
10.2270 + value = String(value || ''); // Make sure we don't try to store or set a non-string value
10.2271 + ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
10.2272 + element[classesWrittenByBindingKey] = value;
10.2273 + ko.utils.toggleDomNodeCssClass(element, value, true);
10.2274 + }
10.2275 + }
10.2276 +};
10.2277 +ko.bindingHandlers['enable'] = {
10.2278 + 'update': function (element, valueAccessor) {
10.2279 + var value = ko.utils.unwrapObservable(valueAccessor());
10.2280 + if (value && element.disabled)
10.2281 + element.removeAttribute("disabled");
10.2282 + else if ((!value) && (!element.disabled))
10.2283 + element.disabled = true;
10.2284 + }
10.2285 +};
10.2286 +
10.2287 +ko.bindingHandlers['disable'] = {
10.2288 + 'update': function (element, valueAccessor) {
10.2289 + ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
10.2290 + }
10.2291 +};
10.2292 +// For certain common events (currently just 'click'), allow a simplified data-binding syntax
10.2293 +// e.g. click:handler instead of the usual full-length event:{click:handler}
10.2294 +function makeEventHandlerShortcut(eventName) {
10.2295 + ko.bindingHandlers[eventName] = {
10.2296 + 'init': function(element, valueAccessor, allBindingsAccessor, viewModel) {
10.2297 + var newValueAccessor = function () {
10.2298 + var result = {};
10.2299 + result[eventName] = valueAccessor();
10.2300 + return result;
10.2301 + };
10.2302 + return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindingsAccessor, viewModel);
10.2303 + }
10.2304 + }
10.2305 +}
10.2306 +
10.2307 +ko.bindingHandlers['event'] = {
10.2308 + 'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) {
10.2309 + var eventsToHandle = valueAccessor() || {};
10.2310 + for(var eventNameOutsideClosure in eventsToHandle) {
10.2311 + (function() {
10.2312 + var eventName = eventNameOutsideClosure; // Separate variable to be captured by event handler closure
10.2313 + if (typeof eventName == "string") {
10.2314 + ko.utils.registerEventHandler(element, eventName, function (event) {
10.2315 + var handlerReturnValue;
10.2316 + var handlerFunction = valueAccessor()[eventName];
10.2317 + if (!handlerFunction)
10.2318 + return;
10.2319 + var allBindings = allBindingsAccessor();
10.2320 +
10.2321 + try {
10.2322 + // Take all the event args, and prefix with the viewmodel
10.2323 + var argsForHandler = ko.utils.makeArray(arguments);
10.2324 + argsForHandler.unshift(viewModel);
10.2325 + handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);
10.2326 + } finally {
10.2327 + if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
10.2328 + if (event.preventDefault)
10.2329 + event.preventDefault();
10.2330 + else
10.2331 + event.returnValue = false;
10.2332 + }
10.2333 + }
10.2334 +
10.2335 + var bubble = allBindings[eventName + 'Bubble'] !== false;
10.2336 + if (!bubble) {
10.2337 + event.cancelBubble = true;
10.2338 + if (event.stopPropagation)
10.2339 + event.stopPropagation();
10.2340 + }
10.2341 + });
10.2342 + }
10.2343 + })();
10.2344 + }
10.2345 + }
10.2346 +};
10.2347 +// "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
10.2348 +// "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
10.2349 +ko.bindingHandlers['foreach'] = {
10.2350 + makeTemplateValueAccessor: function(valueAccessor) {
10.2351 + return function() {
10.2352 + var modelValue = valueAccessor(),
10.2353 + unwrappedValue = ko.utils.peekObservable(modelValue); // Unwrap without setting a dependency here
10.2354 +
10.2355 + // If unwrappedValue is the array, pass in the wrapped value on its own
10.2356 + // The value will be unwrapped and tracked within the template binding
10.2357 + // (See https://github.com/SteveSanderson/knockout/issues/523)
10.2358 + if ((!unwrappedValue) || typeof unwrappedValue.length == "number")
10.2359 + return { 'foreach': modelValue, 'templateEngine': ko.nativeTemplateEngine.instance };
10.2360 +
10.2361 + // If unwrappedValue.data is the array, preserve all relevant options and unwrap again value so we get updates
10.2362 + ko.utils.unwrapObservable(modelValue);
10.2363 + return {
10.2364 + 'foreach': unwrappedValue['data'],
10.2365 + 'as': unwrappedValue['as'],
10.2366 + 'includeDestroyed': unwrappedValue['includeDestroyed'],
10.2367 + 'afterAdd': unwrappedValue['afterAdd'],
10.2368 + 'beforeRemove': unwrappedValue['beforeRemove'],
10.2369 + 'afterRender': unwrappedValue['afterRender'],
10.2370 + 'beforeMove': unwrappedValue['beforeMove'],
10.2371 + 'afterMove': unwrappedValue['afterMove'],
10.2372 + 'templateEngine': ko.nativeTemplateEngine.instance
10.2373 + };
10.2374 + };
10.2375 + },
10.2376 + 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
10.2377 + return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
10.2378 + },
10.2379 + 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
10.2380 + return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
10.2381 + }
10.2382 +};
10.2383 +ko.expressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
10.2384 +ko.virtualElements.allowedBindings['foreach'] = true;
10.2385 +var hasfocusUpdatingProperty = '__ko_hasfocusUpdating';
10.2386 +ko.bindingHandlers['hasfocus'] = {
10.2387 + 'init': function(element, valueAccessor, allBindingsAccessor) {
10.2388 + var handleElementFocusChange = function(isFocused) {
10.2389 + // Where possible, ignore which event was raised and determine focus state using activeElement,
10.2390 + // as this avoids phantom focus/blur events raised when changing tabs in modern browsers.
10.2391 + // However, not all KO-targeted browsers (Firefox 2) support activeElement. For those browsers,
10.2392 + // prevent a loss of focus when changing tabs/windows by setting a flag that prevents hasfocus
10.2393 + // from calling 'blur()' on the element when it loses focus.
10.2394 + // Discussion at https://github.com/SteveSanderson/knockout/pull/352
10.2395 + element[hasfocusUpdatingProperty] = true;
10.2396 + var ownerDoc = element.ownerDocument;
10.2397 + if ("activeElement" in ownerDoc) {
10.2398 + isFocused = (ownerDoc.activeElement === element);
10.2399 + }
10.2400 + var modelValue = valueAccessor();
10.2401 + ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'hasfocus', isFocused, true);
10.2402 + element[hasfocusUpdatingProperty] = false;
10.2403 + };
10.2404 + var handleElementFocusIn = handleElementFocusChange.bind(null, true);
10.2405 + var handleElementFocusOut = handleElementFocusChange.bind(null, false);
10.2406 +
10.2407 + ko.utils.registerEventHandler(element, "focus", handleElementFocusIn);
10.2408 + ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE
10.2409 + ko.utils.registerEventHandler(element, "blur", handleElementFocusOut);
10.2410 + ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
10.2411 + },
10.2412 + 'update': function(element, valueAccessor) {
10.2413 + var value = ko.utils.unwrapObservable(valueAccessor());
10.2414 + if (!element[hasfocusUpdatingProperty]) {
10.2415 + value ? element.focus() : element.blur();
10.2416 + ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
10.2417 + }
10.2418 + }
10.2419 +};
10.2420 +ko.bindingHandlers['html'] = {
10.2421 + 'init': function() {
10.2422 + // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
10.2423 + return { 'controlsDescendantBindings': true };
10.2424 + },
10.2425 + 'update': function (element, valueAccessor) {
10.2426 + // setHtml will unwrap the value if needed
10.2427 + ko.utils.setHtml(element, valueAccessor());
10.2428 + }
10.2429 +};
10.2430 +var withIfDomDataKey = '__ko_withIfBindingData';
10.2431 +// Makes a binding like with or if
10.2432 +function makeWithIfBinding(bindingKey, isWith, isNot, makeContextCallback) {
10.2433 + ko.bindingHandlers[bindingKey] = {
10.2434 + 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
10.2435 + ko.utils.domData.set(element, withIfDomDataKey, {});
10.2436 + return { 'controlsDescendantBindings': true };
10.2437 + },
10.2438 + 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
10.2439 + var withIfData = ko.utils.domData.get(element, withIfDomDataKey),
10.2440 + dataValue = ko.utils.unwrapObservable(valueAccessor()),
10.2441 + shouldDisplay = !isNot !== !dataValue, // equivalent to isNot ? !dataValue : !!dataValue
10.2442 + isFirstRender = !withIfData.savedNodes,
10.2443 + needsRefresh = isFirstRender || isWith || (shouldDisplay !== withIfData.didDisplayOnLastUpdate);
10.2444 +
10.2445 + if (needsRefresh) {
10.2446 + if (isFirstRender) {
10.2447 + withIfData.savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */);
10.2448 + }
10.2449 +
10.2450 + if (shouldDisplay) {
10.2451 + if (!isFirstRender) {
10.2452 + ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(withIfData.savedNodes));
10.2453 + }
10.2454 + ko.applyBindingsToDescendants(makeContextCallback ? makeContextCallback(bindingContext, dataValue) : bindingContext, element);
10.2455 + } else {
10.2456 + ko.virtualElements.emptyNode(element);
10.2457 + }
10.2458 +
10.2459 + withIfData.didDisplayOnLastUpdate = shouldDisplay;
10.2460 + }
10.2461 + }
10.2462 + };
10.2463 + ko.expressionRewriting.bindingRewriteValidators[bindingKey] = false; // Can't rewrite control flow bindings
10.2464 + ko.virtualElements.allowedBindings[bindingKey] = true;
10.2465 +}
10.2466 +
10.2467 +// Construct the actual binding handlers
10.2468 +makeWithIfBinding('if');
10.2469 +makeWithIfBinding('ifnot', false /* isWith */, true /* isNot */);
10.2470 +makeWithIfBinding('with', true /* isWith */, false /* isNot */,
10.2471 + function(bindingContext, dataValue) {
10.2472 + return bindingContext['createChildContext'](dataValue);
10.2473 + }
10.2474 +);
10.2475 +function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) {
10.2476 + if (preferModelValue) {
10.2477 + if (modelValue !== ko.selectExtensions.readValue(element))
10.2478 + ko.selectExtensions.writeValue(element, modelValue);
10.2479 + }
10.2480 +
10.2481 + // No matter which direction we're syncing in, we want the end result to be equality between dropdown value and model value.
10.2482 + // If they aren't equal, either we prefer the dropdown value, or the model value couldn't be represented, so either way,
10.2483 + // change the model value to match the dropdown.
10.2484 + if (modelValue !== ko.selectExtensions.readValue(element))
10.2485 + ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
10.2486 +};
10.2487 +
10.2488 +ko.bindingHandlers['options'] = {
10.2489 + 'update': function (element, valueAccessor, allBindingsAccessor) {
10.2490 + if (ko.utils.tagNameLower(element) !== "select")
10.2491 + throw new Error("options binding applies only to SELECT elements");
10.2492 +
10.2493 + var selectWasPreviouslyEmpty = element.length == 0;
10.2494 + var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
10.2495 + return node.tagName && (ko.utils.tagNameLower(node) === "option") && node.selected;
10.2496 + }), function (node) {
10.2497 + return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
10.2498 + });
10.2499 + var previousScrollTop = element.scrollTop;
10.2500 +
10.2501 + var value = ko.utils.unwrapObservable(valueAccessor());
10.2502 + var selectedValue = element.value;
10.2503 +
10.2504 + // Remove all existing <option>s.
10.2505 + // Need to use .remove() rather than .removeChild() for <option>s otherwise IE behaves oddly (https://github.com/SteveSanderson/knockout/issues/134)
10.2506 + while (element.length > 0) {
10.2507 + ko.cleanNode(element.options[0]);
10.2508 + element.remove(0);
10.2509 + }
10.2510 +
10.2511 + if (value) {
10.2512 + var allBindings = allBindingsAccessor(),
10.2513 + includeDestroyed = allBindings['optionsIncludeDestroyed'];
10.2514 +
10.2515 + if (typeof value.length != "number")
10.2516 + value = [value];
10.2517 + if (allBindings['optionsCaption']) {
10.2518 + var option = document.createElement("option");
10.2519 + ko.utils.setHtml(option, allBindings['optionsCaption']);
10.2520 + ko.selectExtensions.writeValue(option, undefined);
10.2521 + element.appendChild(option);
10.2522 + }
10.2523 +
10.2524 + for (var i = 0, j = value.length; i < j; i++) {
10.2525 + // Skip destroyed items
10.2526 + var arrayEntry = value[i];
10.2527 + if (arrayEntry && arrayEntry['_destroy'] && !includeDestroyed)
10.2528 + continue;
10.2529 +
10.2530 + var option = document.createElement("option");
10.2531 +
10.2532 + function applyToObject(object, predicate, defaultValue) {
10.2533 + var predicateType = typeof predicate;
10.2534 + if (predicateType == "function") // Given a function; run it against the data value
10.2535 + return predicate(object);
10.2536 + else if (predicateType == "string") // Given a string; treat it as a property name on the data value
10.2537 + return object[predicate];
10.2538 + else // Given no optionsText arg; use the data value itself
10.2539 + return defaultValue;
10.2540 + }
10.2541 +
10.2542 + // Apply a value to the option element
10.2543 + var optionValue = applyToObject(arrayEntry, allBindings['optionsValue'], arrayEntry);
10.2544 + ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue));
10.2545 +
10.2546 + // Apply some text to the option element
10.2547 + var optionText = applyToObject(arrayEntry, allBindings['optionsText'], optionValue);
10.2548 + ko.utils.setTextContent(option, optionText);
10.2549 +
10.2550 + element.appendChild(option);
10.2551 + }
10.2552 +
10.2553 + // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
10.2554 + // That's why we first added them without selection. Now it's time to set the selection.
10.2555 + var newOptions = element.getElementsByTagName("option");
10.2556 + var countSelectionsRetained = 0;
10.2557 + for (var i = 0, j = newOptions.length; i < j; i++) {
10.2558 + if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
10.2559 + ko.utils.setOptionNodeSelectionState(newOptions[i], true);
10.2560 + countSelectionsRetained++;
10.2561 + }
10.2562 + }
10.2563 +
10.2564 + element.scrollTop = previousScrollTop;
10.2565 +
10.2566 + if (selectWasPreviouslyEmpty && ('value' in allBindings)) {
10.2567 + // Ensure consistency between model value and selected option.
10.2568 + // If the dropdown is being populated for the first time here (or was otherwise previously empty),
10.2569 + // the dropdown selection state is meaningless, so we preserve the model value.
10.2570 + ensureDropdownSelectionIsConsistentWithModelValue(element, ko.utils.peekObservable(allBindings['value']), /* preferModelValue */ true);
10.2571 + }
10.2572 +
10.2573 + // Workaround for IE9 bug
10.2574 + ko.utils.ensureSelectElementIsRenderedCorrectly(element);
10.2575 + }
10.2576 + }
10.2577 +};
10.2578 +ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.optionValueDomData__';
10.2579 +ko.bindingHandlers['selectedOptions'] = {
10.2580 + 'init': function (element, valueAccessor, allBindingsAccessor) {
10.2581 + ko.utils.registerEventHandler(element, "change", function () {
10.2582 + var value = valueAccessor(), valueToWrite = [];
10.2583 + ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
10.2584 + if (node.selected)
10.2585 + valueToWrite.push(ko.selectExtensions.readValue(node));
10.2586 + });
10.2587 + ko.expressionRewriting.writeValueToProperty(value, allBindingsAccessor, 'value', valueToWrite);
10.2588 + });
10.2589 + },
10.2590 + 'update': function (element, valueAccessor) {
10.2591 + if (ko.utils.tagNameLower(element) != "select")
10.2592 + throw new Error("values binding applies only to SELECT elements");
10.2593 +
10.2594 + var newValue = ko.utils.unwrapObservable(valueAccessor());
10.2595 + if (newValue && typeof newValue.length == "number") {
10.2596 + ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
10.2597 + var isSelected = ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0;
10.2598 + ko.utils.setOptionNodeSelectionState(node, isSelected);
10.2599 + });
10.2600 + }
10.2601 + }
10.2602 +};
10.2603 +ko.bindingHandlers['style'] = {
10.2604 + 'update': function (element, valueAccessor) {
10.2605 + var value = ko.utils.unwrapObservable(valueAccessor() || {});
10.2606 + for (var styleName in value) {
10.2607 + if (typeof styleName == "string") {
10.2608 + var styleValue = ko.utils.unwrapObservable(value[styleName]);
10.2609 + element.style[styleName] = styleValue || ""; // Empty string removes the value, whereas null/undefined have no effect
10.2610 + }
10.2611 + }
10.2612 + }
10.2613 +};
10.2614 +ko.bindingHandlers['submit'] = {
10.2615 + 'init': function (element, valueAccessor, allBindingsAccessor, viewModel) {
10.2616 + if (typeof valueAccessor() != "function")
10.2617 + throw new Error("The value for a submit binding must be a function");
10.2618 + ko.utils.registerEventHandler(element, "submit", function (event) {
10.2619 + var handlerReturnValue;
10.2620 + var value = valueAccessor();
10.2621 + try { handlerReturnValue = value.call(viewModel, element); }
10.2622 + finally {
10.2623 + if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
10.2624 + if (event.preventDefault)
10.2625 + event.preventDefault();
10.2626 + else
10.2627 + event.returnValue = false;
10.2628 + }
10.2629 + }
10.2630 + });
10.2631 + }
10.2632 +};
10.2633 +ko.bindingHandlers['text'] = {
10.2634 + 'update': function (element, valueAccessor) {
10.2635 + ko.utils.setTextContent(element, valueAccessor());
10.2636 + }
10.2637 +};
10.2638 +ko.virtualElements.allowedBindings['text'] = true;
10.2639 +ko.bindingHandlers['uniqueName'] = {
10.2640 + 'init': function (element, valueAccessor) {
10.2641 + if (valueAccessor()) {
10.2642 + var name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
10.2643 + ko.utils.setElementName(element, name);
10.2644 + }
10.2645 + }
10.2646 +};
10.2647 +ko.bindingHandlers['uniqueName'].currentIndex = 0;
10.2648 +ko.bindingHandlers['value'] = {
10.2649 + 'init': function (element, valueAccessor, allBindingsAccessor) {
10.2650 + // Always catch "change" event; possibly other events too if asked
10.2651 + var eventsToCatch = ["change"];
10.2652 + var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
10.2653 + var propertyChangedFired = false;
10.2654 + if (requestedEventsToCatch) {
10.2655 + if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
10.2656 + requestedEventsToCatch = [requestedEventsToCatch];
10.2657 + ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
10.2658 + eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
10.2659 + }
10.2660 +
10.2661 + var valueUpdateHandler = function() {
10.2662 + propertyChangedFired = false;
10.2663 + var modelValue = valueAccessor();
10.2664 + var elementValue = ko.selectExtensions.readValue(element);
10.2665 + ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue);
10.2666 + }
10.2667 +
10.2668 + // Workaround for https://github.com/SteveSanderson/knockout/issues/122
10.2669 + // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
10.2670 + var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
10.2671 + && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
10.2672 + if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
10.2673 + ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
10.2674 + ko.utils.registerEventHandler(element, "blur", function() {
10.2675 + if (propertyChangedFired) {
10.2676 + valueUpdateHandler();
10.2677 + }
10.2678 + });
10.2679 + }
10.2680 +
10.2681 + ko.utils.arrayForEach(eventsToCatch, function(eventName) {
10.2682 + // The syntax "after<eventname>" means "run the handler asynchronously after the event"
10.2683 + // This is useful, for example, to catch "keydown" events after the browser has updated the control
10.2684 + // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
10.2685 + var handler = valueUpdateHandler;
10.2686 + if (ko.utils.stringStartsWith(eventName, "after")) {
10.2687 + handler = function() { setTimeout(valueUpdateHandler, 0) };
10.2688 + eventName = eventName.substring("after".length);
10.2689 + }
10.2690 + ko.utils.registerEventHandler(element, eventName, handler);
10.2691 + });
10.2692 + },
10.2693 + 'update': function (element, valueAccessor) {
10.2694 + var valueIsSelectOption = ko.utils.tagNameLower(element) === "select";
10.2695 + var newValue = ko.utils.unwrapObservable(valueAccessor());
10.2696 + var elementValue = ko.selectExtensions.readValue(element);
10.2697 + var valueHasChanged = (newValue != elementValue);
10.2698 +
10.2699 + // JavaScript's 0 == "" behavious is unfortunate here as it prevents writing 0 to an empty text box (loose equality suggests the values are the same).
10.2700 + // We don't want to do a strict equality comparison as that is more confusing for developers in certain cases, so we specifically special case 0 != "" here.
10.2701 + if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0"))
10.2702 + valueHasChanged = true;
10.2703 +
10.2704 + if (valueHasChanged) {
10.2705 + var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
10.2706 + applyValueAction();
10.2707 +
10.2708 + // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
10.2709 + // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
10.2710 + // to apply the value as well.
10.2711 + var alsoApplyAsynchronously = valueIsSelectOption;
10.2712 + if (alsoApplyAsynchronously)
10.2713 + setTimeout(applyValueAction, 0);
10.2714 + }
10.2715 +
10.2716 + // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
10.2717 + // because you're not allowed to have a model value that disagrees with a visible UI selection.
10.2718 + if (valueIsSelectOption && (element.length > 0))
10.2719 + ensureDropdownSelectionIsConsistentWithModelValue(element, newValue, /* preferModelValue */ false);
10.2720 + }
10.2721 +};
10.2722 +ko.bindingHandlers['visible'] = {
10.2723 + 'update': function (element, valueAccessor) {
10.2724 + var value = ko.utils.unwrapObservable(valueAccessor());
10.2725 + var isCurrentlyVisible = !(element.style.display == "none");
10.2726 + if (value && !isCurrentlyVisible)
10.2727 + element.style.display = "";
10.2728 + else if ((!value) && isCurrentlyVisible)
10.2729 + element.style.display = "none";
10.2730 + }
10.2731 +};
10.2732 +// 'click' is just a shorthand for the usual full-length event:{click:handler}
10.2733 +makeEventHandlerShortcut('click');
10.2734 +// If you want to make a custom template engine,
10.2735 +//
10.2736 +// [1] Inherit from this class (like ko.nativeTemplateEngine does)
10.2737 +// [2] Override 'renderTemplateSource', supplying a function with this signature:
10.2738 +//
10.2739 +// function (templateSource, bindingContext, options) {
10.2740 +// // - templateSource.text() is the text of the template you should render
10.2741 +// // - bindingContext.$data is the data you should pass into the template
10.2742 +// // - you might also want to make bindingContext.$parent, bindingContext.$parents,
10.2743 +// // and bindingContext.$root available in the template too
10.2744 +// // - options gives you access to any other properties set on "data-bind: { template: options }"
10.2745 +// //
10.2746 +// // Return value: an array of DOM nodes
10.2747 +// }
10.2748 +//
10.2749 +// [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
10.2750 +//
10.2751 +// function (script) {
10.2752 +// // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
10.2753 +// // For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
10.2754 +// }
10.2755 +//
10.2756 +// This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
10.2757 +// If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
10.2758 +// and then you don't need to override 'createJavaScriptEvaluatorBlock'.
10.2759 +
10.2760 +ko.templateEngine = function () { };
10.2761 +
10.2762 +ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
10.2763 + throw new Error("Override renderTemplateSource");
10.2764 +};
10.2765 +
10.2766 +ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
10.2767 + throw new Error("Override createJavaScriptEvaluatorBlock");
10.2768 +};
10.2769 +
10.2770 +ko.templateEngine.prototype['makeTemplateSource'] = function(template, templateDocument) {
10.2771 + // Named template
10.2772 + if (typeof template == "string") {
10.2773 + templateDocument = templateDocument || document;
10.2774 + var elem = templateDocument.getElementById(template);
10.2775 + if (!elem)
10.2776 + throw new Error("Cannot find template with ID " + template);
10.2777 + return new ko.templateSources.domElement(elem);
10.2778 + } else if ((template.nodeType == 1) || (template.nodeType == 8)) {
10.2779 + // Anonymous template
10.2780 + return new ko.templateSources.anonymousTemplate(template);
10.2781 + } else
10.2782 + throw new Error("Unknown template type: " + template);
10.2783 +};
10.2784 +
10.2785 +ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) {
10.2786 + var templateSource = this['makeTemplateSource'](template, templateDocument);
10.2787 + return this['renderTemplateSource'](templateSource, bindingContext, options);
10.2788 +};
10.2789 +
10.2790 +ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) {
10.2791 + // Skip rewriting if requested
10.2792 + if (this['allowTemplateRewriting'] === false)
10.2793 + return true;
10.2794 + return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten");
10.2795 +};
10.2796 +
10.2797 +ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, templateDocument) {
10.2798 + var templateSource = this['makeTemplateSource'](template, templateDocument);
10.2799 + var rewritten = rewriterCallback(templateSource['text']());
10.2800 + templateSource['text'](rewritten);
10.2801 + templateSource['data']("isRewritten", true);
10.2802 +};
10.2803 +
10.2804 +ko.exportSymbol('templateEngine', ko.templateEngine);
10.2805 +
10.2806 +ko.templateRewriting = (function () {
10.2807 + var memoizeDataBindingAttributeSyntaxRegex = /(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9\-]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi;
10.2808 + var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
10.2809 +
10.2810 + function validateDataBindValuesForRewriting(keyValueArray) {
10.2811 + var allValidators = ko.expressionRewriting.bindingRewriteValidators;
10.2812 + for (var i = 0; i < keyValueArray.length; i++) {
10.2813 + var key = keyValueArray[i]['key'];
10.2814 + if (allValidators.hasOwnProperty(key)) {
10.2815 + var validator = allValidators[key];
10.2816 +
10.2817 + if (typeof validator === "function") {
10.2818 + var possibleErrorMessage = validator(keyValueArray[i]['value']);
10.2819 + if (possibleErrorMessage)
10.2820 + throw new Error(possibleErrorMessage);
10.2821 + } else if (!validator) {
10.2822 + throw new Error("This template engine does not support the '" + key + "' binding within its templates");
10.2823 + }
10.2824 + }
10.2825 + }
10.2826 + }
10.2827 +
10.2828 + function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, templateEngine) {
10.2829 + var dataBindKeyValueArray = ko.expressionRewriting.parseObjectLiteral(dataBindAttributeValue);
10.2830 + validateDataBindValuesForRewriting(dataBindKeyValueArray);
10.2831 + var rewrittenDataBindAttributeValue = ko.expressionRewriting.preProcessBindings(dataBindKeyValueArray);
10.2832 +
10.2833 + // For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
10.2834 + // anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
10.2835 + // extra indirection.
10.2836 + var applyBindingsToNextSiblingScript =
10.2837 + "ko.__tr_ambtns(function($context,$element){return(function(){return{ " + rewrittenDataBindAttributeValue + " } })()})";
10.2838 + return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
10.2839 + }
10.2840 +
10.2841 + return {
10.2842 + ensureTemplateIsRewritten: function (template, templateEngine, templateDocument) {
10.2843 + if (!templateEngine['isTemplateRewritten'](template, templateDocument))
10.2844 + templateEngine['rewriteTemplate'](template, function (htmlString) {
10.2845 + return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine);
10.2846 + }, templateDocument);
10.2847 + },
10.2848 +
10.2849 + memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
10.2850 + return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
10.2851 + return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[6], /* tagToRetain: */ arguments[1], templateEngine);
10.2852 + }).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
10.2853 + return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", templateEngine);
10.2854 + });
10.2855 + },
10.2856 +
10.2857 + applyMemoizedBindingsToNextSibling: function (bindings) {
10.2858 + return ko.memoization.memoize(function (domNode, bindingContext) {
10.2859 + if (domNode.nextSibling)
10.2860 + ko.applyBindingsToNode(domNode.nextSibling, bindings, bindingContext);
10.2861 + });
10.2862 + }
10.2863 + }
10.2864 +})();
10.2865 +
10.2866 +
10.2867 +// Exported only because it has to be referenced by string lookup from within rewritten template
10.2868 +ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextSibling);
10.2869 +(function() {
10.2870 + // A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving
10.2871 + // logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.)
10.2872 + //
10.2873 + // Two are provided by default:
10.2874 + // 1. ko.templateSources.domElement - reads/writes the text content of an arbitrary DOM element
10.2875 + // 2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but
10.2876 + // without reading/writing the actual element text content, since it will be overwritten
10.2877 + // with the rendered template output.
10.2878 + // You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
10.2879 + // Template sources need to have the following functions:
10.2880 + // text() - returns the template text from your storage location
10.2881 + // text(value) - writes the supplied template text to your storage location
10.2882 + // data(key) - reads values stored using data(key, value) - see below
10.2883 + // data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
10.2884 + //
10.2885 + // Optionally, template sources can also have the following functions:
10.2886 + // nodes() - returns a DOM element containing the nodes of this template, where available
10.2887 + // nodes(value) - writes the given DOM element to your storage location
10.2888 + // If a DOM element is available for a given template source, template engines are encouraged to use it in preference over text()
10.2889 + // for improved speed. However, all templateSources must supply text() even if they don't supply nodes().
10.2890 + //
10.2891 + // Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were
10.2892 + // using and overriding "makeTemplateSource" to return an instance of your custom template source.
10.2893 +
10.2894 + ko.templateSources = {};
10.2895 +
10.2896 + // ---- ko.templateSources.domElement -----
10.2897 +
10.2898 + ko.templateSources.domElement = function(element) {
10.2899 + this.domElement = element;
10.2900 + }
10.2901 +
10.2902 + ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
10.2903 + var tagNameLower = ko.utils.tagNameLower(this.domElement),
10.2904 + elemContentsProperty = tagNameLower === "script" ? "text"
10.2905 + : tagNameLower === "textarea" ? "value"
10.2906 + : "innerHTML";
10.2907 +
10.2908 + if (arguments.length == 0) {
10.2909 + return this.domElement[elemContentsProperty];
10.2910 + } else {
10.2911 + var valueToWrite = arguments[0];
10.2912 + if (elemContentsProperty === "innerHTML")
10.2913 + ko.utils.setHtml(this.domElement, valueToWrite);
10.2914 + else
10.2915 + this.domElement[elemContentsProperty] = valueToWrite;
10.2916 + }
10.2917 + };
10.2918 +
10.2919 + ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
10.2920 + if (arguments.length === 1) {
10.2921 + return ko.utils.domData.get(this.domElement, "templateSourceData_" + key);
10.2922 + } else {
10.2923 + ko.utils.domData.set(this.domElement, "templateSourceData_" + key, arguments[1]);
10.2924 + }
10.2925 + };
10.2926 +
10.2927 + // ---- ko.templateSources.anonymousTemplate -----
10.2928 + // Anonymous templates are normally saved/retrieved as DOM nodes through "nodes".
10.2929 + // For compatibility, you can also read "text"; it will be serialized from the nodes on demand.
10.2930 + // Writing to "text" is still supported, but then the template data will not be available as DOM nodes.
10.2931 +
10.2932 + var anonymousTemplatesDomDataKey = "__ko_anon_template__";
10.2933 + ko.templateSources.anonymousTemplate = function(element) {
10.2934 + this.domElement = element;
10.2935 + }
10.2936 + ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
10.2937 + ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
10.2938 + if (arguments.length == 0) {
10.2939 + var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
10.2940 + if (templateData.textData === undefined && templateData.containerData)
10.2941 + templateData.textData = templateData.containerData.innerHTML;
10.2942 + return templateData.textData;
10.2943 + } else {
10.2944 + var valueToWrite = arguments[0];
10.2945 + ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {textData: valueToWrite});
10.2946 + }
10.2947 + };
10.2948 + ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
10.2949 + if (arguments.length == 0) {
10.2950 + var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
10.2951 + return templateData.containerData;
10.2952 + } else {
10.2953 + var valueToWrite = arguments[0];
10.2954 + ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {containerData: valueToWrite});
10.2955 + }
10.2956 + };
10.2957 +
10.2958 + ko.exportSymbol('templateSources', ko.templateSources);
10.2959 + ko.exportSymbol('templateSources.domElement', ko.templateSources.domElement);
10.2960 + ko.exportSymbol('templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate);
10.2961 +})();
10.2962 +(function () {
10.2963 + var _templateEngine;
10.2964 + ko.setTemplateEngine = function (templateEngine) {
10.2965 + if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
10.2966 + throw new Error("templateEngine must inherit from ko.templateEngine");
10.2967 + _templateEngine = templateEngine;
10.2968 + }
10.2969 +
10.2970 + function invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, action) {
10.2971 + var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode);
10.2972 + while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) {
10.2973 + nextInQueue = ko.virtualElements.nextSibling(node);
10.2974 + if (node.nodeType === 1 || node.nodeType === 8)
10.2975 + action(node);
10.2976 + }
10.2977 + }
10.2978 +
10.2979 + function activateBindingsOnContinuousNodeArray(continuousNodeArray, bindingContext) {
10.2980 + // To be used on any nodes that have been rendered by a template and have been inserted into some parent element
10.2981 + // Walks through continuousNodeArray (which *must* be continuous, i.e., an uninterrupted sequence of sibling nodes, because
10.2982 + // the algorithm for walking them relies on this), and for each top-level item in the virtual-element sense,
10.2983 + // (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings
10.2984 + // (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting)
10.2985 +
10.2986 + if (continuousNodeArray.length) {
10.2987 + var firstNode = continuousNodeArray[0], lastNode = continuousNodeArray[continuousNodeArray.length - 1];
10.2988 +
10.2989 + // Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
10.2990 + // whereas a regular applyBindings won't introduce new memoized nodes
10.2991 + invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, function(node) {
10.2992 + ko.applyBindings(bindingContext, node);
10.2993 + });
10.2994 + invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, function(node) {
10.2995 + ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
10.2996 + });
10.2997 + }
10.2998 + }
10.2999 +
10.3000 + function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
10.3001 + return nodeOrNodeArray.nodeType ? nodeOrNodeArray
10.3002 + : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
10.3003 + : null;
10.3004 + }
10.3005 +
10.3006 + function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
10.3007 + options = options || {};
10.3008 + var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
10.3009 + var templateDocument = firstTargetNode && firstTargetNode.ownerDocument;
10.3010 + var templateEngineToUse = (options['templateEngine'] || _templateEngine);
10.3011 + ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument);
10.3012 + var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument);
10.3013 +
10.3014 + // Loosely check result is an array of DOM nodes
10.3015 + if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
10.3016 + throw new Error("Template engine must return an array of DOM nodes");
10.3017 +
10.3018 + var haveAddedNodesToParent = false;
10.3019 + switch (renderMode) {
10.3020 + case "replaceChildren":
10.3021 + ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
10.3022 + haveAddedNodesToParent = true;
10.3023 + break;
10.3024 + case "replaceNode":
10.3025 + ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
10.3026 + haveAddedNodesToParent = true;
10.3027 + break;
10.3028 + case "ignoreTargetNode": break;
10.3029 + default:
10.3030 + throw new Error("Unknown renderMode: " + renderMode);
10.3031 + }
10.3032 +
10.3033 + if (haveAddedNodesToParent) {
10.3034 + activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
10.3035 + if (options['afterRender'])
10.3036 + ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext['$data']]);
10.3037 + }
10.3038 +
10.3039 + return renderedNodesArray;
10.3040 + }
10.3041 +
10.3042 + ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
10.3043 + options = options || {};
10.3044 + if ((options['templateEngine'] || _templateEngine) == undefined)
10.3045 + throw new Error("Set a template engine before calling renderTemplate");
10.3046 + renderMode = renderMode || "replaceChildren";
10.3047 +
10.3048 + if (targetNodeOrNodeArray) {
10.3049 + var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
10.3050 +
10.3051 + var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
10.3052 + var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
10.3053 +
10.3054 + return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
10.3055 + function () {
10.3056 + // Ensure we've got a proper binding context to work with
10.3057 + var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
10.3058 + ? dataOrBindingContext
10.3059 + : new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
10.3060 +
10.3061 + // Support selecting template as a function of the data being rendered
10.3062 + var templateName = typeof(template) == 'function' ? template(bindingContext['$data'], bindingContext) : template;
10.3063 +
10.3064 + var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
10.3065 + if (renderMode == "replaceNode") {
10.3066 + targetNodeOrNodeArray = renderedNodesArray;
10.3067 + firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
10.3068 + }
10.3069 + },
10.3070 + null,
10.3071 + { disposeWhen: whenToDispose, disposeWhenNodeIsRemoved: activelyDisposeWhenNodeIsRemoved }
10.3072 + );
10.3073 + } else {
10.3074 + // We don't yet have a DOM node to evaluate, so use a memo and render the template later when there is a DOM node
10.3075 + return ko.memoization.memoize(function (domNode) {
10.3076 + ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
10.3077 + });
10.3078 + }
10.3079 + };
10.3080 +
10.3081 + ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
10.3082 + // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
10.3083 + // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
10.3084 + var arrayItemContext;
10.3085 +
10.3086 + // This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
10.3087 + var executeTemplateForArrayItem = function (arrayValue, index) {
10.3088 + // Support selecting template as a function of the data being rendered
10.3089 + arrayItemContext = parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue), options['as']);
10.3090 + arrayItemContext['$index'] = index;
10.3091 + var templateName = typeof(template) == 'function' ? template(arrayValue, arrayItemContext) : template;
10.3092 + return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
10.3093 + }
10.3094 +
10.3095 + // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
10.3096 + var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
10.3097 + activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
10.3098 + if (options['afterRender'])
10.3099 + options['afterRender'](addedNodesArray, arrayValue);
10.3100 + };
10.3101 +
10.3102 + return ko.dependentObservable(function () {
10.3103 + var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
10.3104 + if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
10.3105 + unwrappedArray = [unwrappedArray];
10.3106 +
10.3107 + // Filter out any entries marked as destroyed
10.3108 + var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
10.3109 + return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
10.3110 + });
10.3111 +
10.3112 + // Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
10.3113 + // If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
10.3114 + ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback]);
10.3115 +
10.3116 + }, null, { disposeWhenNodeIsRemoved: targetNode });
10.3117 + };
10.3118 +
10.3119 + var templateComputedDomDataKey = '__ko__templateComputedDomDataKey__';
10.3120 + function disposeOldComputedAndStoreNewOne(element, newComputed) {
10.3121 + var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
10.3122 + if (oldComputed && (typeof(oldComputed.dispose) == 'function'))
10.3123 + oldComputed.dispose();
10.3124 + ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
10.3125 + }
10.3126 +
10.3127 + ko.bindingHandlers['template'] = {
10.3128 + 'init': function(element, valueAccessor) {
10.3129 + // Support anonymous templates
10.3130 + var bindingValue = ko.utils.unwrapObservable(valueAccessor());
10.3131 + if ((typeof bindingValue != "string") && (!bindingValue['name']) && (element.nodeType == 1 || element.nodeType == 8)) {
10.3132 + // It's an anonymous template - store the element contents, then clear the element
10.3133 + var templateNodes = element.nodeType == 1 ? element.childNodes : ko.virtualElements.childNodes(element),
10.3134 + container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
10.3135 + new ko.templateSources.anonymousTemplate(element)['nodes'](container);
10.3136 + }
10.3137 + return { 'controlsDescendantBindings': true };
10.3138 + },
10.3139 + 'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
10.3140 + var templateName = ko.utils.unwrapObservable(valueAccessor()),
10.3141 + options = {},
10.3142 + shouldDisplay = true,
10.3143 + dataValue,
10.3144 + templateComputed = null;
10.3145 +
10.3146 + if (typeof templateName != "string") {
10.3147 + options = templateName;
10.3148 + templateName = options['name'];
10.3149 +
10.3150 + // Support "if"/"ifnot" conditions
10.3151 + if ('if' in options)
10.3152 + shouldDisplay = ko.utils.unwrapObservable(options['if']);
10.3153 + if (shouldDisplay && 'ifnot' in options)
10.3154 + shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);
10.3155 +
10.3156 + dataValue = ko.utils.unwrapObservable(options['data']);
10.3157 + }
10.3158 +
10.3159 + if ('foreach' in options) {
10.3160 + // Render once for each data point (treating data set as empty if shouldDisplay==false)
10.3161 + var dataArray = (shouldDisplay && options['foreach']) || [];
10.3162 + templateComputed = ko.renderTemplateForEach(templateName || element, dataArray, options, element, bindingContext);
10.3163 + } else if (!shouldDisplay) {
10.3164 + ko.virtualElements.emptyNode(element);
10.3165 + } else {
10.3166 + // Render once for this single data point (or use the viewModel if no data was provided)
10.3167 + var innerBindingContext = ('data' in options) ?
10.3168 + bindingContext['createChildContext'](dataValue, options['as']) : // Given an explitit 'data' value, we create a child binding context for it
10.3169 + bindingContext; // Given no explicit 'data' value, we retain the same binding context
10.3170 + templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
10.3171 + }
10.3172 +
10.3173 + // It only makes sense to have a single template computed per element (otherwise which one should have its output displayed?)
10.3174 + disposeOldComputedAndStoreNewOne(element, templateComputed);
10.3175 + }
10.3176 + };
10.3177 +
10.3178 + // Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
10.3179 + ko.expressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
10.3180 + var parsedBindingValue = ko.expressionRewriting.parseObjectLiteral(bindingValue);
10.3181 +
10.3182 + if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
10.3183 + return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting)
10.3184 +
10.3185 + if (ko.expressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
10.3186 + return null; // Named templates can be rewritten, so return "no error"
10.3187 + return "This template engine does not support anonymous templates nested within its templates";
10.3188 + };
10.3189 +
10.3190 + ko.virtualElements.allowedBindings['template'] = true;
10.3191 +})();
10.3192 +
10.3193 +ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
10.3194 +ko.exportSymbol('renderTemplate', ko.renderTemplate);
10.3195 +
10.3196 +ko.utils.compareArrays = (function () {
10.3197 + var statusNotInOld = 'added', statusNotInNew = 'deleted';
10.3198 +
10.3199 + // Simple calculation based on Levenshtein distance.
10.3200 + function compareArrays(oldArray, newArray, dontLimitMoves) {
10.3201 + oldArray = oldArray || [];
10.3202 + newArray = newArray || [];
10.3203 +
10.3204 + if (oldArray.length <= newArray.length)
10.3205 + return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, dontLimitMoves);
10.3206 + else
10.3207 + return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, dontLimitMoves);
10.3208 + }
10.3209 +
10.3210 + function compareSmallArrayToBigArray(smlArray, bigArray, statusNotInSml, statusNotInBig, dontLimitMoves) {
10.3211 + var myMin = Math.min,
10.3212 + myMax = Math.max,
10.3213 + editDistanceMatrix = [],
10.3214 + smlIndex, smlIndexMax = smlArray.length,
10.3215 + bigIndex, bigIndexMax = bigArray.length,
10.3216 + compareRange = (bigIndexMax - smlIndexMax) || 1,
10.3217 + maxDistance = smlIndexMax + bigIndexMax + 1,
10.3218 + thisRow, lastRow,
10.3219 + bigIndexMaxForRow, bigIndexMinForRow;
10.3220 +
10.3221 + for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
10.3222 + lastRow = thisRow;
10.3223 + editDistanceMatrix.push(thisRow = []);
10.3224 + bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
10.3225 + bigIndexMinForRow = myMax(0, smlIndex - 1);
10.3226 + for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) {
10.3227 + if (!bigIndex)
10.3228 + thisRow[bigIndex] = smlIndex + 1;
10.3229 + else if (!smlIndex) // Top row - transform empty array into new array via additions
10.3230 + thisRow[bigIndex] = bigIndex + 1;
10.3231 + else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1])
10.3232 + thisRow[bigIndex] = lastRow[bigIndex - 1]; // copy value (no edit)
10.3233 + else {
10.3234 + var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion)
10.3235 + var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition)
10.3236 + thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
10.3237 + }
10.3238 + }
10.3239 + }
10.3240 +
10.3241 + var editScript = [], meMinusOne, notInSml = [], notInBig = [];
10.3242 + for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
10.3243 + meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
10.3244 + if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
10.3245 + notInSml.push(editScript[editScript.length] = { // added
10.3246 + 'status': statusNotInSml,
10.3247 + 'value': bigArray[--bigIndex],
10.3248 + 'index': bigIndex });
10.3249 + } else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) {
10.3250 + notInBig.push(editScript[editScript.length] = { // deleted
10.3251 + 'status': statusNotInBig,
10.3252 + 'value': smlArray[--smlIndex],
10.3253 + 'index': smlIndex });
10.3254 + } else {
10.3255 + editScript.push({
10.3256 + 'status': "retained",
10.3257 + 'value': bigArray[--bigIndex] });
10.3258 + --smlIndex;
10.3259 + }
10.3260 + }
10.3261 +
10.3262 + if (notInSml.length && notInBig.length) {
10.3263 + // Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
10.3264 + // smlIndexMax keeps the time complexity of this algorithm linear.
10.3265 + var limitFailedCompares = smlIndexMax * 10, failedCompares,
10.3266 + a, d, notInSmlItem, notInBigItem;
10.3267 + // Go through the items that have been added and deleted and try to find matches between them.
10.3268 + for (failedCompares = a = 0; (dontLimitMoves || failedCompares < limitFailedCompares) && (notInSmlItem = notInSml[a]); a++) {
10.3269 + for (d = 0; notInBigItem = notInBig[d]; d++) {
10.3270 + if (notInSmlItem['value'] === notInBigItem['value']) {
10.3271 + notInSmlItem['moved'] = notInBigItem['index'];
10.3272 + notInBigItem['moved'] = notInSmlItem['index'];
10.3273 + notInBig.splice(d,1); // This item is marked as moved; so remove it from notInBig list
10.3274 + failedCompares = d = 0; // Reset failed compares count because we're checking for consecutive failures
10.3275 + break;
10.3276 + }
10.3277 + }
10.3278 + failedCompares += d;
10.3279 + }
10.3280 + }
10.3281 + return editScript.reverse();
10.3282 + }
10.3283 +
10.3284 + return compareArrays;
10.3285 +})();
10.3286 +
10.3287 +ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
10.3288 +
10.3289 +(function () {
10.3290 + // Objective:
10.3291 + // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
10.3292 + // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
10.3293 + // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
10.3294 + // so that its children is again the concatenation of the mappings of the array elements, but don't re-map any array elements that we
10.3295 + // previously mapped - retain those nodes, and just insert/delete other ones
10.3296 +
10.3297 + // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
10.3298 + // You can use this, for example, to activate bindings on those nodes.
10.3299 +
10.3300 + function fixUpNodesToBeMovedOrRemoved(contiguousNodeArray) {
10.3301 + // Before moving, deleting, or replacing a set of nodes that were previously outputted by the "map" function, we have to reconcile
10.3302 + // them against what is in the DOM right now. It may be that some of the nodes have already been removed from the document,
10.3303 + // or that new nodes might have been inserted in the middle, for example by a binding. Also, there may previously have been
10.3304 + // leading comment nodes (created by rewritten string-based templates) that have since been removed during binding.
10.3305 + // So, this function translates the old "map" output array into its best guess of what set of current DOM nodes should be removed.
10.3306 + //
10.3307 + // Rules:
10.3308 + // [A] Any leading nodes that aren't in the document any more should be ignored
10.3309 + // These most likely correspond to memoization nodes that were already removed during binding
10.3310 + // See https://github.com/SteveSanderson/knockout/pull/440
10.3311 + // [B] We want to output a contiguous series of nodes that are still in the document. So, ignore any nodes that
10.3312 + // have already been removed, and include any nodes that have been inserted among the previous collection
10.3313 +
10.3314 + // Rule [A]
10.3315 + while (contiguousNodeArray.length && !ko.utils.domNodeIsAttachedToDocument(contiguousNodeArray[0]))
10.3316 + contiguousNodeArray.splice(0, 1);
10.3317 +
10.3318 + // Rule [B]
10.3319 + if (contiguousNodeArray.length > 1) {
10.3320 + // Build up the actual new contiguous node set
10.3321 + var current = contiguousNodeArray[0], last = contiguousNodeArray[contiguousNodeArray.length - 1], newContiguousSet = [current];
10.3322 + while (current !== last) {
10.3323 + current = current.nextSibling;
10.3324 + if (!current) // Won't happen, except if the developer has manually removed some DOM elements (then we're in an undefined scenario)
10.3325 + return;
10.3326 + newContiguousSet.push(current);
10.3327 + }
10.3328 +
10.3329 + // ... then mutate the input array to match this.
10.3330 + // (The following line replaces the contents of contiguousNodeArray with newContiguousSet)
10.3331 + Array.prototype.splice.apply(contiguousNodeArray, [0, contiguousNodeArray.length].concat(newContiguousSet));
10.3332 + }
10.3333 + return contiguousNodeArray;
10.3334 + }
10.3335 +
10.3336 + function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
10.3337 + // Map this array value inside a dependentObservable so we re-map when any dependency changes
10.3338 + var mappedNodes = [];
10.3339 + var dependentObservable = ko.dependentObservable(function() {
10.3340 + var newMappedNodes = mapping(valueToMap, index) || [];
10.3341 +
10.3342 + // On subsequent evaluations, just replace the previously-inserted DOM nodes
10.3343 + if (mappedNodes.length > 0) {
10.3344 + ko.utils.replaceDomNodes(fixUpNodesToBeMovedOrRemoved(mappedNodes), newMappedNodes);
10.3345 + if (callbackAfterAddingNodes)
10.3346 + ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]);
10.3347 + }
10.3348 +
10.3349 + // Replace the contents of the mappedNodes array, thereby updating the record
10.3350 + // of which nodes would be deleted if valueToMap was itself later removed
10.3351 + mappedNodes.splice(0, mappedNodes.length);
10.3352 + ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
10.3353 + }, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return (mappedNodes.length == 0) || !ko.utils.domNodeIsAttachedToDocument(mappedNodes[0]) } });
10.3354 + return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
10.3355 + }
10.3356 +
10.3357 + var lastMappingResultDomDataKey = "setDomNodeChildrenFromArrayMapping_lastMappingResult";
10.3358 +
10.3359 + ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
10.3360 + // Compare the provided array against the previous one
10.3361 + array = array || [];
10.3362 + options = options || {};
10.3363 + var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
10.3364 + var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
10.3365 + var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
10.3366 + var editScript = ko.utils.compareArrays(lastArray, array);
10.3367 +
10.3368 + // Build the new mapping result
10.3369 + var newMappingResult = [];
10.3370 + var lastMappingResultIndex = 0;
10.3371 + var newMappingResultIndex = 0;
10.3372 +
10.3373 + var nodesToDelete = [];
10.3374 + var itemsToProcess = [];
10.3375 + var itemsForBeforeRemoveCallbacks = [];
10.3376 + var itemsForMoveCallbacks = [];
10.3377 + var itemsForAfterAddCallbacks = [];
10.3378 + var mapData;
10.3379 +
10.3380 + function itemMovedOrRetained(editScriptIndex, oldPosition) {
10.3381 + mapData = lastMappingResult[oldPosition];
10.3382 + if (newMappingResultIndex !== oldPosition)
10.3383 + itemsForMoveCallbacks[editScriptIndex] = mapData;
10.3384 + // Since updating the index might change the nodes, do so before calling fixUpNodesToBeMovedOrRemoved
10.3385 + mapData.indexObservable(newMappingResultIndex++);
10.3386 + fixUpNodesToBeMovedOrRemoved(mapData.mappedNodes);
10.3387 + newMappingResult.push(mapData);
10.3388 + itemsToProcess.push(mapData);
10.3389 + }
10.3390 +
10.3391 + function callCallback(callback, items) {
10.3392 + if (callback) {
10.3393 + for (var i = 0, n = items.length; i < n; i++) {
10.3394 + if (items[i]) {
10.3395 + ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
10.3396 + callback(node, i, items[i].arrayEntry);
10.3397 + });
10.3398 + }
10.3399 + }
10.3400 + }
10.3401 + }
10.3402 +
10.3403 + for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
10.3404 + movedIndex = editScriptItem['moved'];
10.3405 + switch (editScriptItem['status']) {
10.3406 + case "deleted":
10.3407 + if (movedIndex === undefined) {
10.3408 + mapData = lastMappingResult[lastMappingResultIndex];
10.3409 +
10.3410 + // Stop tracking changes to the mapping for these nodes
10.3411 + if (mapData.dependentObservable)
10.3412 + mapData.dependentObservable.dispose();
10.3413 +
10.3414 + // Queue these nodes for later removal
10.3415 + nodesToDelete.push.apply(nodesToDelete, fixUpNodesToBeMovedOrRemoved(mapData.mappedNodes));
10.3416 + if (options['beforeRemove']) {
10.3417 + itemsForBeforeRemoveCallbacks[i] = mapData;
10.3418 + itemsToProcess.push(mapData);
10.3419 + }
10.3420 + }
10.3421 + lastMappingResultIndex++;
10.3422 + break;
10.3423 +
10.3424 + case "retained":
10.3425 + itemMovedOrRetained(i, lastMappingResultIndex++);
10.3426 + break;
10.3427 +
10.3428 + case "added":
10.3429 + if (movedIndex !== undefined) {
10.3430 + itemMovedOrRetained(i, movedIndex);
10.3431 + } else {
10.3432 + mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
10.3433 + newMappingResult.push(mapData);
10.3434 + itemsToProcess.push(mapData);
10.3435 + if (!isFirstExecution)
10.3436 + itemsForAfterAddCallbacks[i] = mapData;
10.3437 + }
10.3438 + break;
10.3439 + }
10.3440 + }
10.3441 +
10.3442 + // Call beforeMove first before any changes have been made to the DOM
10.3443 + callCallback(options['beforeMove'], itemsForMoveCallbacks);
10.3444 +
10.3445 + // Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
10.3446 + ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
10.3447 +
10.3448 + // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
10.3449 + for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
10.3450 + // Get nodes for newly added items
10.3451 + if (!mapData.mappedNodes)
10.3452 + ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
10.3453 +
10.3454 + // Put nodes in the right place if they aren't there already
10.3455 + for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
10.3456 + if (node !== nextNode)
10.3457 + ko.virtualElements.insertAfter(domNode, node, lastNode);
10.3458 + }
10.3459 +
10.3460 + // Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
10.3461 + if (!mapData.initialized && callbackAfterAddingNodes) {
10.3462 + callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
10.3463 + mapData.initialized = true;
10.3464 + }
10.3465 + }
10.3466 +
10.3467 + // If there's a beforeRemove callback, call it after reordering.
10.3468 + // Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
10.3469 + // some sort of animation, which is why we first reorder the nodes that will be removed. If the
10.3470 + // callback instead removes the nodes right away, it would be more efficient to skip reordering them.
10.3471 + // Perhaps we'll make that change in the future if this scenario becomes more common.
10.3472 + callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
10.3473 +
10.3474 + // Finally call afterMove and afterAdd callbacks
10.3475 + callCallback(options['afterMove'], itemsForMoveCallbacks);
10.3476 + callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
10.3477 +
10.3478 + // Store a copy of the array items we just considered so we can difference it next time
10.3479 + ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
10.3480 + }
10.3481 +})();
10.3482 +
10.3483 +ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
10.3484 +ko.nativeTemplateEngine = function () {
10.3485 + this['allowTemplateRewriting'] = false;
10.3486 +}
10.3487 +
10.3488 +ko.nativeTemplateEngine.prototype = new ko.templateEngine();
10.3489 +ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
10.3490 + var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly
10.3491 + templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null,
10.3492 + templateNodes = templateNodesFunc ? templateSource['nodes']() : null;
10.3493 +
10.3494 + if (templateNodes) {
10.3495 + return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes);
10.3496 + } else {
10.3497 + var templateText = templateSource['text']();
10.3498 + return ko.utils.parseHtmlFragment(templateText);
10.3499 + }
10.3500 +};
10.3501 +
10.3502 +ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine();
10.3503 +ko.setTemplateEngine(ko.nativeTemplateEngine.instance);
10.3504 +
10.3505 +ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
10.3506 +(function() {
10.3507 + ko.jqueryTmplTemplateEngine = function () {
10.3508 + // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
10.3509 + // doesn't expose a version number, so we have to infer it.
10.3510 + // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
10.3511 + // which KO internally refers to as version "2", so older versions are no longer detected.
10.3512 + var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
10.3513 + if ((typeof(jQuery) == "undefined") || !(jQuery['tmpl']))
10.3514 + return 0;
10.3515 + // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
10.3516 + try {
10.3517 + if (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
10.3518 + // Since 1.0.0pre, custom tags should append markup to an array called "__"
10.3519 + return 2; // Final version of jquery.tmpl
10.3520 + }
10.3521 + } catch(ex) { /* Apparently not the version we were looking for */ }
10.3522 +
10.3523 + return 1; // Any older version that we don't support
10.3524 + })();
10.3525 +
10.3526 + function ensureHasReferencedJQueryTemplates() {
10.3527 + if (jQueryTmplVersion < 2)
10.3528 + throw new Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");
10.3529 + }
10.3530 +
10.3531 + function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
10.3532 + return jQuery['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
10.3533 + }
10.3534 +
10.3535 + this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
10.3536 + options = options || {};
10.3537 + ensureHasReferencedJQueryTemplates();
10.3538 +
10.3539 + // Ensure we have stored a precompiled version of this template (don't want to reparse on every render)
10.3540 + var precompiled = templateSource['data']('precompiled');
10.3541 + if (!precompiled) {
10.3542 + var templateText = templateSource['text']() || "";
10.3543 + // Wrap in "with($whatever.koBindingContext) { ... }"
10.3544 + templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
10.3545 +
10.3546 + precompiled = jQuery['template'](null, templateText);
10.3547 + templateSource['data']('precompiled', precompiled);
10.3548 + }
10.3549 +
10.3550 + var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
10.3551 + var jQueryTemplateOptions = jQuery['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
10.3552 +
10.3553 + var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
10.3554 + resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
10.3555 +
10.3556 + jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
10.3557 + return resultNodes;
10.3558 + };
10.3559 +
10.3560 + this['createJavaScriptEvaluatorBlock'] = function(script) {
10.3561 + return "{{ko_code ((function() { return " + script + " })()) }}";
10.3562 + };
10.3563 +
10.3564 + this['addTemplate'] = function(templateName, templateMarkup) {
10.3565 + document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
10.3566 + };
10.3567 +
10.3568 + if (jQueryTmplVersion > 0) {
10.3569 + jQuery['tmpl']['tag']['ko_code'] = {
10.3570 + open: "__.push($1 || '');"
10.3571 + };
10.3572 + jQuery['tmpl']['tag']['ko_with'] = {
10.3573 + open: "with($1) {",
10.3574 + close: "} "
10.3575 + };
10.3576 + }
10.3577 + };
10.3578 +
10.3579 + ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
10.3580 +
10.3581 + // Use this one by default *only if jquery.tmpl is referenced*
10.3582 + var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
10.3583 + if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
10.3584 + ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
10.3585 +
10.3586 + ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
10.3587 +})();
10.3588 +});
10.3589 +})(window,document,navigator,window["jQuery"]);
10.3590 +})();
10.3591 \ No newline at end of file
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
11.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Wed Jan 23 10:57:05 2013 +0100
11.3 @@ -0,0 +1,62 @@
11.4 +/**
11.5 + * Back 2 Browser Bytecode Translator
11.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
11.7 + *
11.8 + * This program is free software: you can redistribute it and/or modify
11.9 + * it under the terms of the GNU General Public License as published by
11.10 + * the Free Software Foundation, version 2 of the License.
11.11 + *
11.12 + * This program is distributed in the hope that it will be useful,
11.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
11.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11.15 + * GNU General Public License for more details.
11.16 + *
11.17 + * You should have received a copy of the GNU General Public License
11.18 + * along with this program. Look for COPYING file in the top folder.
11.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
11.20 + */
11.21 +package org.apidesign.bck2brwsr.htmlpage;
11.22 +
11.23 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
11.24 +import org.apidesign.bck2brwsr.htmlpage.api.OnEvent;
11.25 +import org.apidesign.bck2brwsr.htmlpage.api.Page;
11.26 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
11.27 +import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
11.28 +import org.apidesign.bck2brwsr.vmtest.HtmlFragment;
11.29 +import org.apidesign.bck2brwsr.vmtest.VMTest;
11.30 +import org.testng.annotations.Factory;
11.31 +
11.32 +/**
11.33 + *
11.34 + * @author Jaroslav Tulach <jtulach@netbeans.org>
11.35 + */
11.36 +@Page(xhtml="Knockout.xhtml", className="KnockoutModel", properties={
11.37 + @Property(name="name", type=String.class)
11.38 +})
11.39 +public class KnockoutTest {
11.40 +
11.41 + @HtmlFragment(
11.42 + "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
11.43 + "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
11.44 + "<button id=\"hello\">Say Hello!</button>\n"
11.45 + )
11.46 + @BrwsrTest public void modifyValueAssertChangeInModel() {
11.47 + KnockoutModel m = new KnockoutModel();
11.48 + m.setName("Kukuc");
11.49 + m.applyBindings();
11.50 + assert "Kukuc".equals(m.INPUT.getValue()) : "Value is really kukuc: " + m.INPUT.getValue();
11.51 + m.INPUT.setValue("Jardo");
11.52 + m.triggerEvent(m.INPUT, OnEvent.CHANGE);
11.53 + assert "Jardo".equals(m.getName()) : "Name property updated: " + m.getName();
11.54 + }
11.55 +
11.56 + @ComputedProperty
11.57 + static String helloMessage(String name) {
11.58 + return "Hello " + name + "!";
11.59 + }
11.60 +
11.61 + @Factory
11.62 + public static Object[] create() {
11.63 + return VMTest.create(KnockoutTest.class);
11.64 + }
11.65 +}
12.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Tue Jan 22 19:21:34 2013 +0100
12.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Wed Jan 23 10:57:05 2013 +0100
12.3 @@ -17,18 +17,103 @@
12.4 */
12.5 package org.apidesign.bck2brwsr.htmlpage;
12.6
12.7 +import java.util.ArrayList;
12.8 +import java.util.List;
12.9 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
12.10 import org.apidesign.bck2brwsr.htmlpage.api.Page;
12.11 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
12.12 import static org.testng.Assert.*;
12.13 +import org.testng.annotations.BeforeMethod;
12.14 import org.testng.annotations.Test;
12.15
12.16 /**
12.17 *
12.18 * @author Jaroslav Tulach <jtulach@netbeans.org>
12.19 */
12.20 -@Page(xhtml = "Empty.html", className = "Model")
12.21 +@Page(xhtml = "Empty.html", className = "Model", properties = {
12.22 + @Property(name = "value", type = int.class),
12.23 + @Property(name = "unrelated", type = long.class)
12.24 +})
12.25 public class ModelTest {
12.26 - @Test public void classGenerated() {
12.27 - Class<?> c = Model.class;
12.28 - assertNotNull(c, "Class for empty page generated");
12.29 + private Model model;
12.30 + private static Model leakedModel;
12.31 +
12.32 + @BeforeMethod
12.33 + public void createModel() {
12.34 + model = new Model();
12.35 + }
12.36 +
12.37 + @Test public void classGeneratedWithSetterGetter() {
12.38 + model.setValue(10);
12.39 + assertEquals(10, model.getValue(), "Value changed");
12.40 + }
12.41 +
12.42 + @Test public void computedMethod() {
12.43 + model.setValue(4);
12.44 + assertEquals(16, model.getPowerValue());
12.45 + }
12.46 +
12.47 + @Test public void derivedPropertiesAreNotified() {
12.48 + MockKnockout my = new MockKnockout();
12.49 + MockKnockout.next = my;
12.50 +
12.51 + model.applyBindings();
12.52 +
12.53 + model.setValue(33);
12.54 +
12.55 + assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated);
12.56 + assertTrue(my.mutated.contains("powerValue"), "Power value is in there: " + my.mutated);
12.57 + assertTrue(my.mutated.contains("value"), "Simple value is in there: " + my.mutated);
12.58 +
12.59 + my.mutated.clear();
12.60 +
12.61 + model.setUnrelated(44);
12.62 + assertEquals(my.mutated.size(), 1, "One property changed");
12.63 + assertTrue(my.mutated.contains("unrelated"), "Its name is unrelated");
12.64 + }
12.65 +
12.66 + @Test public void computedPropertyCannotWriteToModel() {
12.67 + leakedModel = model;
12.68 + try {
12.69 + String res = model.getNotAllowedWrite();
12.70 + fail("We should not be allowed to write to the model: " + res);
12.71 + } catch (IllegalStateException ex) {
12.72 + // OK, we can't read
12.73 + }
12.74 + }
12.75 +
12.76 + @Test public void computedPropertyCannotReadToModel() {
12.77 + leakedModel = model;
12.78 + try {
12.79 + String res = model.getNotAllowedRead();
12.80 + fail("We should not be allowed to read from the model: " + res);
12.81 + } catch (IllegalStateException ex) {
12.82 + // OK, we can't read
12.83 + }
12.84 + }
12.85 +
12.86 + @ComputedProperty
12.87 + static int powerValue(int value) {
12.88 + return value * value;
12.89 + }
12.90 +
12.91 + @ComputedProperty
12.92 + static String notAllowedRead() {
12.93 + return "Not allowed callback: " + leakedModel.getUnrelated();
12.94 + }
12.95 +
12.96 + @ComputedProperty
12.97 + static String notAllowedWrite() {
12.98 + leakedModel.setUnrelated(11);
12.99 + return "Not allowed callback!";
12.100 + }
12.101 +
12.102 + static class MockKnockout extends Knockout {
12.103 + List<String> mutated = new ArrayList<String>();
12.104 +
12.105 + @Override
12.106 + public void valueHasMutated(String prop) {
12.107 + mutated.add(prop);
12.108 + }
12.109 }
12.110 }
13.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java Tue Jan 22 19:21:34 2013 +0100
13.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java Wed Jan 23 10:57:05 2013 +0100
13.3 @@ -43,9 +43,14 @@
13.4 */
13.5 @Page(xhtml="TestPage.html")
13.6 public class PageController {
13.7 + private static final TestPage PAGE = new TestPage();
13.8 +
13.9 @On(event = CLICK, id="pg.button")
13.10 - static void updateTitle() {
13.11 - TestPage.PG_TITLE.setText("You want this window to be named " + TestPage.PG_TEXT.getValue());
13.12 + static void updateTitle(TestPage ref) {
13.13 + if (PAGE != ref) {
13.14 + throw new IllegalStateException("Both references should be the same. " + ref + " != " + PAGE);
13.15 + }
13.16 + ref.PG_TITLE.setText("You want this window to be named " + ref.PG_TEXT.getValue());
13.17 }
13.18
13.19 @On(event = CLICK, id={ "pg.title", "pg.text" })
13.20 @@ -53,6 +58,6 @@
13.21 if (!id.equals("pg.title")) {
13.22 throw new IllegalStateException();
13.23 }
13.24 - TestPage.PG_TITLE.setText(id);
13.25 + PAGE.PG_TITLE.setText(id);
13.26 }
13.27 }
14.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ProcessPageTest.java Tue Jan 22 19:21:34 2013 +0100
14.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ProcessPageTest.java Wed Jan 23 10:57:05 2013 +0100
14.3 @@ -108,7 +108,9 @@
14.4 + "\n"
14.5 + "window.document = doc;\n"
14.6 );
14.7 - Invocable i = compileClass(sb, "org/apidesign/bck2brwsr/htmlpage/PageController");
14.8 + Invocable i = compileClass(sb,
14.9 + "org/apidesign/bck2brwsr/htmlpage/PageController"
14.10 + );
14.11
14.12 Object ret = null;
14.13 try {
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
15.2 +++ b/javaquery/api/src/test/resources/org/apidesign/bck2brwsr/htmlpage/Knockout.xhtml Wed Jan 23 10:57:05 2013 +0100
15.3 @@ -0,0 +1,25 @@
15.4 +<?xml version="1.0" encoding="UTF-8"?>
15.5 +<!--
15.6 +
15.7 + Back 2 Browser Bytecode Translator
15.8 + Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
15.9 +
15.10 + This program is free software: you can redistribute it and/or modify
15.11 + it under the terms of the GNU General Public License as published by
15.12 + the Free Software Foundation, version 2 of the License.
15.13 +
15.14 + This program is distributed in the hope that it will be useful,
15.15 + but WITHOUT ANY WARRANTY; without even the implied warranty of
15.16 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15.17 + GNU General Public License for more details.
15.18 +
15.19 + You should have received a copy of the GNU General Public License
15.20 + along with this program. Look for COPYING file in the top folder.
15.21 + If not, see http://opensource.org/licenses/GPL-2.0.
15.22 +
15.23 +-->
15.24 +<p>
15.25 + <h1 data-bind="text: helloMessage">Loading Bck2Brwsr's Hello World...</h1>
15.26 + Your name: <input id="input" data-bind="value: name, valueUpdate: 'afterkeydown'"></input>
15.27 + <button id="hello">Say Hello!</button>
15.28 +</p>
16.1 --- a/javaquery/demo-calculator-dynamic/pom.xml Tue Jan 22 19:21:34 2013 +0100
16.2 +++ b/javaquery/demo-calculator-dynamic/pom.xml Wed Jan 23 10:57:05 2013 +0100
16.3 @@ -28,7 +28,7 @@
16.4 </execution>
16.5 </executions>
16.6 <configuration>
16.7 - <startpage>org/apidesign/bck2brwsr/mavenhtml/Calculator.xhtml</startpage>
16.8 + <startpage>org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml</startpage>
16.9 </configuration>
16.10 </plugin>
16.11 <plugin>
16.12 @@ -54,5 +54,11 @@
16.13 <artifactId>javaquery.api</artifactId>
16.14 <version>0.3-SNAPSHOT</version>
16.15 </dependency>
16.16 + <dependency>
16.17 + <groupId>org.testng</groupId>
16.18 + <artifactId>testng</artifactId>
16.19 + <version>6.5.2</version>
16.20 + <scope>test</scope>
16.21 + </dependency>
16.22 </dependencies>
16.23 </project>
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
17.2 +++ b/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java Wed Jan 23 10:57:05 2013 +0100
17.3 @@ -0,0 +1,112 @@
17.4 +/**
17.5 + * Back 2 Browser Bytecode Translator
17.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
17.7 + *
17.8 + * This program is free software: you can redistribute it and/or modify
17.9 + * it under the terms of the GNU General Public License as published by
17.10 + * the Free Software Foundation, version 2 of the License.
17.11 + *
17.12 + * This program is distributed in the hope that it will be useful,
17.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
17.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17.15 + * GNU General Public License for more details.
17.16 + *
17.17 + * You should have received a copy of the GNU General Public License
17.18 + * along with this program. Look for COPYING file in the top folder.
17.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
17.20 + */
17.21 +package org.apidesign.bck2brwsr.demo.calc;
17.22 +
17.23 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
17.24 +import org.apidesign.bck2brwsr.htmlpage.api.On;
17.25 +import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*;
17.26 +import org.apidesign.bck2brwsr.htmlpage.api.Page;
17.27 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
17.28 +
17.29 +/** HTML5 & Java demo showing the power of
17.30 + * <a href="http://wiki.apidesign.org/wiki/AnnotationProcessor">annotation processors</a>
17.31 + * as well as other goodies.
17.32 + *
17.33 + * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
17.34 + */
17.35 +@Page(xhtml="Calculator.xhtml", properties = {
17.36 + @Property(name = "memory", type = double.class),
17.37 + @Property(name = "display", type = double.class),
17.38 + @Property(name = "operation", type = String.class),
17.39 + @Property(name = "hover", type = boolean.class)
17.40 +})
17.41 +public class Calc {
17.42 + static {
17.43 + new Calculator().applyBindings();
17.44 + }
17.45 +
17.46 + @On(event = CLICK, id="clear")
17.47 + static void clear(Calculator c) {
17.48 + c.setMemory(0);
17.49 + c.setOperation(null);
17.50 + c.setDisplay(0);
17.51 + }
17.52 +
17.53 + @On(event = CLICK, id= { "plus", "minus", "mul", "div" })
17.54 + static void applyOp(Calculator c, String op) {
17.55 + c.setMemory(c.getDisplay());
17.56 + c.setOperation(op);
17.57 + c.setDisplay(0);
17.58 + }
17.59 +
17.60 + @On(event = MOUSE_OVER, id= { "result" })
17.61 + static void attemptingIn(Calculator c, String op) {
17.62 + c.setHover(true);
17.63 + }
17.64 + @On(event = MOUSE_OUT, id= { "result" })
17.65 + static void attemptingOut(Calculator c, String op) {
17.66 + c.setHover(false);
17.67 + }
17.68 +
17.69 + @On(event = CLICK, id="result")
17.70 + static void computeTheValue(Calculator c) {
17.71 + c.setDisplay(compute(
17.72 + c.getOperation(),
17.73 + c.getMemory(),
17.74 + c.getDisplay()
17.75 + ));
17.76 + c.setMemory(0);
17.77 + }
17.78 +
17.79 + private static double compute(String op, double memory, double display) {
17.80 + switch (op) {
17.81 + case "plus": return memory + display;
17.82 + case "minus": return memory - display;
17.83 + case "mul": return memory * display;
17.84 + case "div": return memory / display;
17.85 + default: throw new IllegalStateException(op);
17.86 + }
17.87 + }
17.88 +
17.89 + @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"})
17.90 + static void addDigit(String digit, Calculator c) {
17.91 + digit = digit.substring(1);
17.92 +
17.93 + double v = c.getDisplay();
17.94 + if (v == 0.0) {
17.95 + c.setDisplay(Integer.parseInt(digit));
17.96 + } else {
17.97 + String txt = Double.toString(v);
17.98 + if (txt.endsWith(".0")) {
17.99 + txt = txt.substring(0, txt.length() - 2);
17.100 + }
17.101 + txt = txt + digit;
17.102 + c.setDisplay(Double.parseDouble(txt));
17.103 + }
17.104 + }
17.105 +
17.106 + @ComputedProperty
17.107 + public static String displayPreview(
17.108 + double display, boolean hover, double memory, String operation
17.109 + ) {
17.110 + if (!hover) {
17.111 + return "Type numbers and perform simple operations! Press '=' to get result.";
17.112 + }
17.113 + return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display);
17.114 + }
17.115 +}
18.1 --- a/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/mavenhtml/App.java Tue Jan 22 19:21:34 2013 +0100
18.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
18.3 @@ -1,89 +0,0 @@
18.4 -/**
18.5 - * Back 2 Browser Bytecode Translator
18.6 - * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
18.7 - *
18.8 - * This program is free software: you can redistribute it and/or modify
18.9 - * it under the terms of the GNU General Public License as published by
18.10 - * the Free Software Foundation, version 2 of the License.
18.11 - *
18.12 - * This program is distributed in the hope that it will be useful,
18.13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
18.14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18.15 - * GNU General Public License for more details.
18.16 - *
18.17 - * You should have received a copy of the GNU General Public License
18.18 - * along with this program. Look for COPYING file in the top folder.
18.19 - * If not, see http://opensource.org/licenses/GPL-2.0.
18.20 - */
18.21 -package org.apidesign.bck2brwsr.mavenhtml;
18.22 -
18.23 -import org.apidesign.bck2brwsr.htmlpage.api.On;
18.24 -import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*;
18.25 -import org.apidesign.bck2brwsr.htmlpage.api.Page;
18.26 -
18.27 -/** HTML5 & Java demo showing the power of
18.28 - * <a href="http://wiki.apidesign.org/wiki/AnnotationProcessor">annotation processors</a>
18.29 - * as well as other goodies.
18.30 - *
18.31 - * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
18.32 - */
18.33 -@Page(xhtml="Calculator.xhtml")
18.34 -public class App {
18.35 - private static double memory;
18.36 - private static String operation;
18.37 -
18.38 - @On(event = CLICK, id="clear")
18.39 - static void clear() {
18.40 - memory = 0;
18.41 - operation = null;
18.42 - Calculator.DISPLAY.setValue("0");
18.43 - }
18.44 -
18.45 - @On(event = CLICK, id= { "plus", "minus", "mul", "div" })
18.46 - static void applyOp(String op) {
18.47 - memory = getValue();
18.48 - operation = op;
18.49 - Calculator.DISPLAY.setValue("0");
18.50 - }
18.51 -
18.52 - @On(event = CLICK, id="result")
18.53 - static void computeTheValue() {
18.54 - switch (operation) {
18.55 - case "plus": setValue(memory + getValue()); break;
18.56 - case "minus": setValue(memory - getValue()); break;
18.57 - case "mul": setValue(memory * getValue()); break;
18.58 - case "div": setValue(memory / getValue()); break;
18.59 - default: throw new IllegalStateException(operation);
18.60 - }
18.61 - }
18.62 -
18.63 - @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"})
18.64 - static void addDigit(String digit) {
18.65 - digit = digit.substring(1);
18.66 - String v = Calculator.DISPLAY.getValue();
18.67 - if (getValue() == 0.0) {
18.68 - Calculator.DISPLAY.setValue(digit);
18.69 - } else {
18.70 - Calculator.DISPLAY.setValue(v + digit);
18.71 - }
18.72 - }
18.73 -
18.74 - private static void setValue(double v) {
18.75 - StringBuilder sb = new StringBuilder();
18.76 - sb.append(v);
18.77 - if (sb.toString().endsWith(".0")) {
18.78 - final int l = sb.length();
18.79 - sb.delete(l - 2, l);
18.80 - }
18.81 - Calculator.DISPLAY.setValue(sb.toString());
18.82 - }
18.83 -
18.84 - private static double getValue() {
18.85 - try {
18.86 - return Double.parseDouble(Calculator.DISPLAY.getValue());
18.87 - } catch (NumberFormatException ex) {
18.88 - Calculator.DISPLAY.setValue("err");
18.89 - return 0.0;
18.90 - }
18.91 - }
18.92 -}
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
19.2 +++ b/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml Wed Jan 23 10:57:05 2013 +0100
19.3 @@ -0,0 +1,159 @@
19.4 +<?xml version="1.0" encoding="UTF-8"?>
19.5 +<!--
19.6 +
19.7 + Back 2 Browser Bytecode Translator
19.8 + Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
19.9 +
19.10 + This program is free software: you can redistribute it and/or modify
19.11 + it under the terms of the GNU General Public License as published by
19.12 + the Free Software Foundation, version 2 of the License.
19.13 +
19.14 + This program is distributed in the hope that it will be useful,
19.15 + but WITHOUT ANY WARRANTY; without even the implied warranty of
19.16 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19.17 + GNU General Public License for more details.
19.18 +
19.19 + You should have received a copy of the GNU General Public License
19.20 + along with this program. Look for COPYING file in the top folder.
19.21 + If not, see http://opensource.org/licenses/GPL-2.0.
19.22 +
19.23 +-->
19.24 +<!DOCTYPE html>
19.25 +<html xmlns="http://www.w3.org/1999/xhtml">
19.26 + <head>
19.27 + <title>Simple Calculator in HTML5 and Java</title>
19.28 +
19.29 + <style type="text/css">
19.30 + body {color: #ffffff; background-color: #121e31; font-family: Monospaced}
19.31 + pre {color: #ffffff; background-color: #121e31; font-family: Monospaced}
19.32 + table {color: #ffffff; background-color: #121e31; font-family: Monospaced}
19.33 + .string {color: #e2ce00}
19.34 + a {color: #e2ce00}
19.35 + .ST1 {color: #0000cc; font-family: Monospaced; font-weight: bold}
19.36 + .ST0 {color: #0000ff}
19.37 + .comment {color: #428bdd}
19.38 + .keyword-directive {color: #f8bb00}
19.39 + .tag {color: #f8bb00}
19.40 + .ST0 {color: #628fb5; background-color: #1b3450}
19.41 + .sgml-comment {color: #808080}
19.42 + .value {color: #99006b}
19.43 + .argument {color: #007c00}
19.44 + .sgml-declaration {color: #bf9221}
19.45 + </style>
19.46 + </head>
19.47 + <body>
19.48 + <h1>Java and HTML5 - Together at Last!</h1>
19.49 + <table border="0" cellspacing="2">
19.50 + <tbody>
19.51 + <tr>
19.52 + <td colspan="4"><input data-bind="value: display" value="0"
19.53 + style="text-align: right"/>
19.54 + </td>
19.55 + </tr>
19.56 + <tr>
19.57 + <td><button id="n1">1</button></td>
19.58 + <td><button id="n2">2</button></td>
19.59 + <td><button id="n3">3</button></td>
19.60 + <td><button id="plus">+</button></td>
19.61 + </tr>
19.62 + <tr>
19.63 + <td><button id="n4">4</button></td>
19.64 + <td><button id="n5">5</button></td>
19.65 + <td><button id="n6">6</button></td>
19.66 + <td><button id="minus">-</button></td>
19.67 + </tr>
19.68 + <tr>
19.69 + <td><button id="n7">7</button></td>
19.70 + <td><button id="n8">8</button></td>
19.71 + <td><button id="n9">9</button></td>
19.72 + <td><button id="mul">*</button></td>
19.73 + </tr>
19.74 + <tr>
19.75 + <td><button id="clear">C</button></td>
19.76 + <td><button id="n0">0</button></td>
19.77 + <td><button id="result">=</button></td>
19.78 + <td><button id="div">/</button></td>
19.79 + </tr>
19.80 + </tbody>
19.81 + </table>
19.82 + <div data-bind="text: displayPreview"></div>
19.83 +
19.84 + <script src="/bck2brwsr.js"></script>
19.85 + <script src="/vm.js"></script>
19.86 + <script type="text/javascript">
19.87 + vm.loadClass('org.apidesign.bck2brwsr.demo.calc.Calc');
19.88 + </script>
19.89 +
19.90 + <hr/>
19.91 + <pre>
19.92 + <span class="keyword-directive">package</span> org.apidesign.bck2brwsr.mavenhtml;
19.93 +
19.94 + <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.OnClick;
19.95 + <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.Page;
19.96 +
19.97 + <span class="comment">/**</span> <span class="comment">HTML5</span><span class="comment"> & </span><span class="comment">Java</span> <span class="comment">demo</span> <span class="comment">showing</span> <span class="comment">the</span> <span class="comment">power</span> <span class="comment">of</span> <a href="http://wiki.apidesign.org/wiki/AnnotationProcessor">annotation processors</a>
19.98 + <span class="comment"> * </span><span class="comment">as</span> <span class="comment">well</span> <span class="comment">as</span> <span class="comment">other</span> <span class="comment">goodies</span><span class="comment">, including type-safe association between</span>
19.99 + <span class="comment"> * </span><span class="comment">an XHTML page and Java.</span>
19.100 + <span class="comment"> * </span>
19.101 + <span class="comment"> * </span><span class="ST1">@author</span> <span class="comment">Jaroslav</span> <span class="comment">Tulach</span> <span class="ST0"><jaroslav.tulach@apidesign.org></span>
19.102 + <span class="comment">*/</span>
19.103 + @Page(xhtml=<span class="string">"</span><span class="string">Calculator.xhtml</span><span class="string">"</span>)
19.104 + <span class="keyword-directive">public</span> <span class="keyword-directive">class</span> App {
19.105 + <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> memory;
19.106 + <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> String operation;
19.107 +
19.108 + @OnClick(id=<span class="string">"</span><span class="string">clear</span><span class="string">"</span>)
19.109 + <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> clear() {
19.110 + memory = <span class="number">0</span>;
19.111 + operation = <span class="keyword-directive">null</span>;
19.112 + Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">0</span><span class="string">"</span>);
19.113 + }
19.114 +
19.115 + @OnClick(id= { <span class="string">"</span><span class="string">plus</span><span class="string">"</span>, <span class="string">"</span><span class="string">minus</span><span class="string">"</span>, <span class="string">"</span><span class="string">mul</span><span class="string">"</span>, <span class="string">"</span><span class="string">div</span><span class="string">"</span> })
19.116 + <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> applyOp(String op) {
19.117 + memory = getValue();
19.118 + operation = op;
19.119 + Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">0</span><span class="string">"</span>);
19.120 + }
19.121 +
19.122 + @OnClick(id=<span class="string">"</span><span class="string">result</span><span class="string">"</span>)
19.123 + <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> computeTheValue() {
19.124 + <span class="keyword-directive">switch</span> (operation) {
19.125 + <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">plus</span><span class="string">"</span>: setValue(memory + getValue()); <span class="keyword-directive">break</span>;
19.126 + <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">minus</span><span class="string">"</span>: setValue(memory - getValue()); <span class="keyword-directive">break</span>;
19.127 + <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">mul</span><span class="string">"</span>: setValue(memory * getValue()); <span class="keyword-directive">break</span>;
19.128 + <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">div</span><span class="string">"</span>: setValue(memory / getValue()); <span class="keyword-directive">break</span>;
19.129 + <span class="keyword-directive">default</span>: <span class="keyword-directive">throw</span> <span class="keyword-directive">new</span> IllegalStateException(operation);
19.130 + }
19.131 + }
19.132 +
19.133 + @OnClick(id={<span class="string">"</span><span class="string">n0</span><span class="string">"</span>, <span class="string">"</span><span class="string">n1</span><span class="string">"</span>, <span class="string">"</span><span class="string">n2</span><span class="string">"</span>, <span class="string">"</span><span class="string">n3</span><span class="string">"</span>, <span class="string">"</span><span class="string">n4</span><span class="string">"</span>, <span class="string">"</span><span class="string">n5</span><span class="string">"</span>, <span class="string">"</span><span class="string">n6</span><span class="string">"</span>, <span class="string">"</span><span class="string">n7</span><span class="string">"</span>, <span class="string">"</span><span class="string">n8</span><span class="string">"</span>, <span class="string">"</span><span class="string">n9</span><span class="string">"</span>})
19.134 + <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> addDigit(String digit) {
19.135 + digit = digit.substring(<span class="number">1</span>);
19.136 + String v = Calculator.DISPLAY.getValue();
19.137 + <span class="keyword-directive">if</span> (getValue() == <span class="number">0.0</span>) {
19.138 + Calculator.DISPLAY.setValue(digit);
19.139 + } <span class="keyword-directive">else</span> {
19.140 + Calculator.DISPLAY.setValue(v + digit);
19.141 + }
19.142 + }
19.143 +
19.144 + <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> setValue(<span class="keyword-directive">double</span> v) {
19.145 + StringBuilder sb = <span class="keyword-directive">new</span> StringBuilder();
19.146 + sb.append(v);
19.147 + Calculator.DISPLAY.setValue(sb.toString());
19.148 + }
19.149 +
19.150 + <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> getValue() {
19.151 + <span class="keyword-directive">try</span> {
19.152 + <span class="keyword-directive">return</span> Double.parseDouble(Calculator.DISPLAY.getValue());
19.153 + } <span class="keyword-directive">catch</span> (NumberFormatException ex) {
19.154 + Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">err</span><span class="string">"</span>);
19.155 + <span class="keyword-directive">return</span> <span class="number">0.0</span>;
19.156 + }
19.157 + }
19.158 + }
19.159 +
19.160 + </pre>
19.161 + </body>
19.162 +</html>
20.1 --- a/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/mavenhtml/Calculator.xhtml Tue Jan 22 19:21:34 2013 +0100
20.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
20.3 @@ -1,158 +0,0 @@
20.4 -<?xml version="1.0" encoding="UTF-8"?>
20.5 -<!--
20.6 -
20.7 - Back 2 Browser Bytecode Translator
20.8 - Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
20.9 -
20.10 - This program is free software: you can redistribute it and/or modify
20.11 - it under the terms of the GNU General Public License as published by
20.12 - the Free Software Foundation, version 2 of the License.
20.13 -
20.14 - This program is distributed in the hope that it will be useful,
20.15 - but WITHOUT ANY WARRANTY; without even the implied warranty of
20.16 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20.17 - GNU General Public License for more details.
20.18 -
20.19 - You should have received a copy of the GNU General Public License
20.20 - along with this program. Look for COPYING file in the top folder.
20.21 - If not, see http://opensource.org/licenses/GPL-2.0.
20.22 -
20.23 --->
20.24 -<!DOCTYPE html>
20.25 -<html xmlns="http://www.w3.org/1999/xhtml">
20.26 - <head>
20.27 - <title>Simple Calculator in HTML5 and Java</title>
20.28 -
20.29 - <style type="text/css">
20.30 - body {color: #ffffff; background-color: #121e31; font-family: Monospaced}
20.31 - pre {color: #ffffff; background-color: #121e31; font-family: Monospaced}
20.32 - table {color: #ffffff; background-color: #121e31; font-family: Monospaced}
20.33 - .string {color: #e2ce00}
20.34 - a {color: #e2ce00}
20.35 - .ST1 {color: #0000cc; font-family: Monospaced; font-weight: bold}
20.36 - .ST0 {color: #0000ff}
20.37 - .comment {color: #428bdd}
20.38 - .keyword-directive {color: #f8bb00}
20.39 - .tag {color: #f8bb00}
20.40 - .ST0 {color: #628fb5; background-color: #1b3450}
20.41 - .sgml-comment {color: #808080}
20.42 - .value {color: #99006b}
20.43 - .argument {color: #007c00}
20.44 - .sgml-declaration {color: #bf9221}
20.45 - </style>
20.46 - </head>
20.47 - <body>
20.48 - <h1>Java and HTML5 - Together at Last!</h1>
20.49 - <table border="0" cellspacing="2">
20.50 - <tbody>
20.51 - <tr>
20.52 - <td colspan="4"><input id="display" value="0"
20.53 - style="text-align: right"/>
20.54 - </td>
20.55 - </tr>
20.56 - <tr>
20.57 - <td><button id="n1">1</button></td>
20.58 - <td><button id="n2">2</button></td>
20.59 - <td><button id="n3">3</button></td>
20.60 - <td><button id="plus">+</button></td>
20.61 - </tr>
20.62 - <tr>
20.63 - <td><button id="n4">4</button></td>
20.64 - <td><button id="n5">5</button></td>
20.65 - <td><button id="n6">6</button></td>
20.66 - <td><button id="minus">-</button></td>
20.67 - </tr>
20.68 - <tr>
20.69 - <td><button id="n7">7</button></td>
20.70 - <td><button id="n8">8</button></td>
20.71 - <td><button id="n9">9</button></td>
20.72 - <td><button id="mul">*</button></td>
20.73 - </tr>
20.74 - <tr>
20.75 - <td><button id="clear">C</button></td>
20.76 - <td><button id="n0">0</button></td>
20.77 - <td><button id="result">=</button></td>
20.78 - <td><button id="div">/</button></td>
20.79 - </tr>
20.80 - </tbody>
20.81 - </table>
20.82 -
20.83 - <script src="/bck2brwsr.js"></script>
20.84 - <script src="/vm.js"></script>
20.85 - <script type="text/javascript">
20.86 - vm.loadClass('org.apidesign.bck2brwsr.mavenhtml.Calculator');
20.87 - </script>
20.88 -
20.89 - <hr/>
20.90 - <pre>
20.91 - <span class="keyword-directive">package</span> org.apidesign.bck2brwsr.mavenhtml;
20.92 -
20.93 - <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.OnClick;
20.94 - <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.Page;
20.95 -
20.96 - <span class="comment">/**</span> <span class="comment">HTML5</span><span class="comment"> & </span><span class="comment">Java</span> <span class="comment">demo</span> <span class="comment">showing</span> <span class="comment">the</span> <span class="comment">power</span> <span class="comment">of</span> <a href="http://wiki.apidesign.org/wiki/AnnotationProcessor">annotation processors</a>
20.97 - <span class="comment"> * </span><span class="comment">as</span> <span class="comment">well</span> <span class="comment">as</span> <span class="comment">other</span> <span class="comment">goodies</span><span class="comment">, including type-safe association between</span>
20.98 - <span class="comment"> * </span><span class="comment">an XHTML page and Java.</span>
20.99 - <span class="comment"> * </span>
20.100 - <span class="comment"> * </span><span class="ST1">@author</span> <span class="comment">Jaroslav</span> <span class="comment">Tulach</span> <span class="ST0"><jaroslav.tulach@apidesign.org></span>
20.101 - <span class="comment">*/</span>
20.102 - @Page(xhtml=<span class="string">"</span><span class="string">Calculator.xhtml</span><span class="string">"</span>)
20.103 - <span class="keyword-directive">public</span> <span class="keyword-directive">class</span> App {
20.104 - <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> memory;
20.105 - <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> String operation;
20.106 -
20.107 - @OnClick(id=<span class="string">"</span><span class="string">clear</span><span class="string">"</span>)
20.108 - <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> clear() {
20.109 - memory = <span class="number">0</span>;
20.110 - operation = <span class="keyword-directive">null</span>;
20.111 - Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">0</span><span class="string">"</span>);
20.112 - }
20.113 -
20.114 - @OnClick(id= { <span class="string">"</span><span class="string">plus</span><span class="string">"</span>, <span class="string">"</span><span class="string">minus</span><span class="string">"</span>, <span class="string">"</span><span class="string">mul</span><span class="string">"</span>, <span class="string">"</span><span class="string">div</span><span class="string">"</span> })
20.115 - <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> applyOp(String op) {
20.116 - memory = getValue();
20.117 - operation = op;
20.118 - Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">0</span><span class="string">"</span>);
20.119 - }
20.120 -
20.121 - @OnClick(id=<span class="string">"</span><span class="string">result</span><span class="string">"</span>)
20.122 - <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> computeTheValue() {
20.123 - <span class="keyword-directive">switch</span> (operation) {
20.124 - <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">plus</span><span class="string">"</span>: setValue(memory + getValue()); <span class="keyword-directive">break</span>;
20.125 - <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">minus</span><span class="string">"</span>: setValue(memory - getValue()); <span class="keyword-directive">break</span>;
20.126 - <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">mul</span><span class="string">"</span>: setValue(memory * getValue()); <span class="keyword-directive">break</span>;
20.127 - <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">div</span><span class="string">"</span>: setValue(memory / getValue()); <span class="keyword-directive">break</span>;
20.128 - <span class="keyword-directive">default</span>: <span class="keyword-directive">throw</span> <span class="keyword-directive">new</span> IllegalStateException(operation);
20.129 - }
20.130 - }
20.131 -
20.132 - @OnClick(id={<span class="string">"</span><span class="string">n0</span><span class="string">"</span>, <span class="string">"</span><span class="string">n1</span><span class="string">"</span>, <span class="string">"</span><span class="string">n2</span><span class="string">"</span>, <span class="string">"</span><span class="string">n3</span><span class="string">"</span>, <span class="string">"</span><span class="string">n4</span><span class="string">"</span>, <span class="string">"</span><span class="string">n5</span><span class="string">"</span>, <span class="string">"</span><span class="string">n6</span><span class="string">"</span>, <span class="string">"</span><span class="string">n7</span><span class="string">"</span>, <span class="string">"</span><span class="string">n8</span><span class="string">"</span>, <span class="string">"</span><span class="string">n9</span><span class="string">"</span>})
20.133 - <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> addDigit(String digit) {
20.134 - digit = digit.substring(<span class="number">1</span>);
20.135 - String v = Calculator.DISPLAY.getValue();
20.136 - <span class="keyword-directive">if</span> (getValue() == <span class="number">0.0</span>) {
20.137 - Calculator.DISPLAY.setValue(digit);
20.138 - } <span class="keyword-directive">else</span> {
20.139 - Calculator.DISPLAY.setValue(v + digit);
20.140 - }
20.141 - }
20.142 -
20.143 - <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> setValue(<span class="keyword-directive">double</span> v) {
20.144 - StringBuilder sb = <span class="keyword-directive">new</span> StringBuilder();
20.145 - sb.append(v);
20.146 - Calculator.DISPLAY.setValue(sb.toString());
20.147 - }
20.148 -
20.149 - <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> getValue() {
20.150 - <span class="keyword-directive">try</span> {
20.151 - <span class="keyword-directive">return</span> Double.parseDouble(Calculator.DISPLAY.getValue());
20.152 - } <span class="keyword-directive">catch</span> (NumberFormatException ex) {
20.153 - Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">err</span><span class="string">"</span>);
20.154 - <span class="keyword-directive">return</span> <span class="number">0.0</span>;
20.155 - }
20.156 - }
20.157 - }
20.158 -
20.159 - </pre>
20.160 - </body>
20.161 -</html>
21.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
21.2 +++ b/javaquery/demo-calculator-dynamic/src/test/java/org/apidesign/bck2brwsr/demo/calc/CalcTest.java Wed Jan 23 10:57:05 2013 +0100
21.3 @@ -0,0 +1,46 @@
21.4 +/**
21.5 + * Back 2 Browser Bytecode Translator
21.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
21.7 + *
21.8 + * This program is free software: you can redistribute it and/or modify
21.9 + * it under the terms of the GNU General Public License as published by
21.10 + * the Free Software Foundation, version 2 of the License.
21.11 + *
21.12 + * This program is distributed in the hope that it will be useful,
21.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
21.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21.15 + * GNU General Public License for more details.
21.16 + *
21.17 + * You should have received a copy of the GNU General Public License
21.18 + * along with this program. Look for COPYING file in the top folder.
21.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
21.20 + */
21.21 +package org.apidesign.bck2brwsr.demo.calc;
21.22 +
21.23 +import static org.testng.Assert.*;
21.24 +import org.testng.annotations.BeforeMethod;
21.25 +import org.testng.annotations.Test;
21.26 +
21.27 +/** Demonstrating POJO testing of HTML page model.
21.28 + *
21.29 + * @author Jaroslav Tulach <jtulach@netbeans.org>
21.30 + */
21.31 +public class CalcTest {
21.32 + private Calculator model;
21.33 +
21.34 +
21.35 + @BeforeMethod
21.36 + public void initModel() {
21.37 + model = new Calculator().applyBindings();
21.38 + }
21.39 +
21.40 + @Test
21.41 + public void testSomeMethod() {
21.42 + model.setDisplay(10);
21.43 + Calc.applyOp(model, "plus");
21.44 + assertEquals(0.0, model.getDisplay(), "Cleared after pressing +");
21.45 + model.setDisplay(5);
21.46 + Calc.computeTheValue(model);
21.47 + assertEquals(15.0, model.getDisplay(), "Shows fifteen");
21.48 + }
21.49 +}
22.1 --- a/javaquery/demo-calculator/pom.xml Tue Jan 22 19:21:34 2013 +0100
22.2 +++ b/javaquery/demo-calculator/pom.xml Wed Jan 23 10:57:05 2013 +0100
22.3 @@ -42,7 +42,7 @@
22.4 <configuration>
22.5 <executable>xdg-open</executable>
22.6 <arguments>
22.7 - <argument>${project.build.directory}/classes/org/apidesign/bck2brwsr/mavenhtml/Calculator.xhtml</argument>
22.8 + <argument>${project.build.directory}/classes/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml</argument>
22.9 </arguments>
22.10 </configuration>
22.11 </plugin>
23.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
23.2 +++ b/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java Wed Jan 23 10:57:05 2013 +0100
23.3 @@ -0,0 +1,112 @@
23.4 +/**
23.5 + * Back 2 Browser Bytecode Translator
23.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
23.7 + *
23.8 + * This program is free software: you can redistribute it and/or modify
23.9 + * it under the terms of the GNU General Public License as published by
23.10 + * the Free Software Foundation, version 2 of the License.
23.11 + *
23.12 + * This program is distributed in the hope that it will be useful,
23.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
23.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23.15 + * GNU General Public License for more details.
23.16 + *
23.17 + * You should have received a copy of the GNU General Public License
23.18 + * along with this program. Look for COPYING file in the top folder.
23.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
23.20 + */
23.21 +package org.apidesign.bck2brwsr.demo.calc.staticcompilation;
23.22 +
23.23 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
23.24 +import org.apidesign.bck2brwsr.htmlpage.api.On;
23.25 +import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*;
23.26 +import org.apidesign.bck2brwsr.htmlpage.api.Page;
23.27 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
23.28 +
23.29 +/** HTML5 & Java demo showing the power of
23.30 + * <a href="http://wiki.apidesign.org/wiki/AnnotationProcessor">annotation processors</a>
23.31 + * as well as other goodies.
23.32 + *
23.33 + * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
23.34 + */
23.35 +@Page(xhtml="Calculator.xhtml", properties = {
23.36 + @Property(name = "memory", type = double.class),
23.37 + @Property(name = "display", type = double.class),
23.38 + @Property(name = "operation", type = String.class),
23.39 + @Property(name = "hover", type = boolean.class)
23.40 +})
23.41 +public class Calc {
23.42 + static {
23.43 + new Calculator().applyBindings();
23.44 + }
23.45 +
23.46 + @On(event = CLICK, id="clear")
23.47 + static void clear(Calculator c) {
23.48 + c.setMemory(0);
23.49 + c.setOperation(null);
23.50 + c.setDisplay(0);
23.51 + }
23.52 +
23.53 + @On(event = CLICK, id= { "plus", "minus", "mul", "div" })
23.54 + static void applyOp(Calculator c, String op) {
23.55 + c.setMemory(c.getDisplay());
23.56 + c.setOperation(op);
23.57 + c.setDisplay(0);
23.58 + }
23.59 +
23.60 + @On(event = MOUSE_OVER, id= { "result" })
23.61 + static void attemptingIn(Calculator c, String op) {
23.62 + c.setHover(true);
23.63 + }
23.64 + @On(event = MOUSE_OUT, id= { "result" })
23.65 + static void attemptingOut(Calculator c, String op) {
23.66 + c.setHover(false);
23.67 + }
23.68 +
23.69 + @On(event = CLICK, id="result")
23.70 + static void computeTheValue(Calculator c) {
23.71 + c.setDisplay(compute(
23.72 + c.getOperation(),
23.73 + c.getMemory(),
23.74 + c.getDisplay()
23.75 + ));
23.76 + c.setMemory(0);
23.77 + }
23.78 +
23.79 + private static double compute(String op, double memory, double display) {
23.80 + switch (op) {
23.81 + case "plus": return memory + display;
23.82 + case "minus": return memory - display;
23.83 + case "mul": return memory * display;
23.84 + case "div": return memory / display;
23.85 + default: throw new IllegalStateException(op);
23.86 + }
23.87 + }
23.88 +
23.89 + @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"})
23.90 + static void addDigit(String digit, Calculator c) {
23.91 + digit = digit.substring(1);
23.92 +
23.93 + double v = c.getDisplay();
23.94 + if (v == 0.0) {
23.95 + c.setDisplay(Integer.parseInt(digit));
23.96 + } else {
23.97 + String txt = Double.toString(v);
23.98 + if (txt.endsWith(".0")) {
23.99 + txt = txt.substring(0, txt.length() - 2);
23.100 + }
23.101 + txt = txt + digit;
23.102 + c.setDisplay(Double.parseDouble(txt));
23.103 + }
23.104 + }
23.105 +
23.106 + @ComputedProperty
23.107 + public static String displayPreview(
23.108 + double display, boolean hover, double memory, String operation
23.109 + ) {
23.110 + if (!hover) {
23.111 + return "Type numbers and perform simple operations! Press '=' to get result.";
23.112 + }
23.113 + return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display);
23.114 + }
23.115 +}
24.1 --- a/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/mavenhtml/App.java Tue Jan 22 19:21:34 2013 +0100
24.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
24.3 @@ -1,89 +0,0 @@
24.4 -/**
24.5 - * Back 2 Browser Bytecode Translator
24.6 - * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
24.7 - *
24.8 - * This program is free software: you can redistribute it and/or modify
24.9 - * it under the terms of the GNU General Public License as published by
24.10 - * the Free Software Foundation, version 2 of the License.
24.11 - *
24.12 - * This program is distributed in the hope that it will be useful,
24.13 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
24.14 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24.15 - * GNU General Public License for more details.
24.16 - *
24.17 - * You should have received a copy of the GNU General Public License
24.18 - * along with this program. Look for COPYING file in the top folder.
24.19 - * If not, see http://opensource.org/licenses/GPL-2.0.
24.20 - */
24.21 -package org.apidesign.bck2brwsr.mavenhtml;
24.22 -
24.23 -import org.apidesign.bck2brwsr.htmlpage.api.On;
24.24 -import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*;
24.25 -import org.apidesign.bck2brwsr.htmlpage.api.Page;
24.26 -
24.27 -/** HTML5 & Java demo showing the power of
24.28 - * <a href="http://wiki.apidesign.org/wiki/AnnotationProcessor">annotation processors</a>
24.29 - * as well as other goodies.
24.30 - *
24.31 - * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
24.32 - */
24.33 -@Page(xhtml="Calculator.xhtml")
24.34 -public class App {
24.35 - private static double memory;
24.36 - private static String operation;
24.37 -
24.38 - @On(event = CLICK, id="clear")
24.39 - static void clear() {
24.40 - memory = 0;
24.41 - operation = null;
24.42 - Calculator.DISPLAY.setValue("0");
24.43 - }
24.44 -
24.45 - @On(event = CLICK, id= { "plus", "minus", "mul", "div" })
24.46 - static void applyOp(String op) {
24.47 - memory = getValue();
24.48 - operation = op;
24.49 - Calculator.DISPLAY.setValue("0");
24.50 - }
24.51 -
24.52 - @On(event = CLICK, id="result")
24.53 - static void computeTheValue() {
24.54 - switch (operation) {
24.55 - case "plus": setValue(memory + getValue()); break;
24.56 - case "minus": setValue(memory - getValue()); break;
24.57 - case "mul": setValue(memory * getValue()); break;
24.58 - case "div": setValue(memory / getValue()); break;
24.59 - default: throw new IllegalStateException(operation);
24.60 - }
24.61 - }
24.62 -
24.63 - @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"})
24.64 - static void addDigit(String digit) {
24.65 - digit = digit.substring(1);
24.66 - String v = Calculator.DISPLAY.getValue();
24.67 - if (getValue() == 0.0) {
24.68 - Calculator.DISPLAY.setValue(digit);
24.69 - } else {
24.70 - Calculator.DISPLAY.setValue(v + digit);
24.71 - }
24.72 - }
24.73 -
24.74 - private static void setValue(double v) {
24.75 - StringBuilder sb = new StringBuilder();
24.76 - sb.append(v);
24.77 - if (sb.toString().endsWith(".0")) {
24.78 - final int l = sb.length();
24.79 - sb.delete(l - 2, l);
24.80 - }
24.81 - Calculator.DISPLAY.setValue(sb.toString());
24.82 - }
24.83 -
24.84 - private static double getValue() {
24.85 - try {
24.86 - return Double.parseDouble(Calculator.DISPLAY.getValue());
24.87 - } catch (NumberFormatException ex) {
24.88 - Calculator.DISPLAY.setValue("err");
24.89 - return 0.0;
24.90 - }
24.91 - }
24.92 -}
25.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
25.2 +++ b/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml Wed Jan 23 10:57:05 2013 +0100
25.3 @@ -0,0 +1,154 @@
25.4 +<?xml version="1.0" encoding="UTF-8"?>
25.5 +<!--
25.6 +
25.7 + Back 2 Browser Bytecode Translator
25.8 + Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
25.9 +
25.10 + This program is free software: you can redistribute it and/or modify
25.11 + it under the terms of the GNU General Public License as published by
25.12 + the Free Software Foundation, version 2 of the License.
25.13 +
25.14 + This program is distributed in the hope that it will be useful,
25.15 + but WITHOUT ANY WARRANTY; without even the implied warranty of
25.16 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25.17 + GNU General Public License for more details.
25.18 +
25.19 + You should have received a copy of the GNU General Public License
25.20 + along with this program. Look for COPYING file in the top folder.
25.21 + If not, see http://opensource.org/licenses/GPL-2.0.
25.22 +
25.23 +-->
25.24 +<!DOCTYPE html>
25.25 +<html xmlns="http://www.w3.org/1999/xhtml">
25.26 + <head>
25.27 + <title>Simple Calculator in HTML5 and Java</title>
25.28 +
25.29 + <style type="text/css">
25.30 + body {color: #ffffff; background-color: #121e31; font-family: Monospaced}
25.31 + pre {color: #ffffff; background-color: #121e31; font-family: Monospaced}
25.32 + table {color: #ffffff; background-color: #121e31; font-family: Monospaced}
25.33 + .string {color: #e2ce00}
25.34 + a {color: #e2ce00}
25.35 + .ST1 {color: #0000cc; font-family: Monospaced; font-weight: bold}
25.36 + .ST0 {color: #0000ff}
25.37 + .comment {color: #428bdd}
25.38 + .keyword-directive {color: #f8bb00}
25.39 + .tag {color: #f8bb00}
25.40 + .ST0 {color: #628fb5; background-color: #1b3450}
25.41 + .sgml-comment {color: #808080}
25.42 + .value {color: #99006b}
25.43 + .argument {color: #007c00}
25.44 + .sgml-declaration {color: #bf9221}
25.45 + </style>
25.46 + </head>
25.47 + <body>
25.48 + <h1>Java and HTML5 - Together at Last!</h1>
25.49 + <table border="0" cellspacing="2">
25.50 + <tbody>
25.51 + <tr>
25.52 + <td colspan="4"><input data-bind="value: display" value="0"
25.53 + style="text-align: right"/>
25.54 + </td>
25.55 + </tr>
25.56 + <tr>
25.57 + <td><button id="n1">1</button></td>
25.58 + <td><button id="n2">2</button></td>
25.59 + <td><button id="n3">3</button></td>
25.60 + <td><button id="plus">+</button></td>
25.61 + </tr>
25.62 + <tr>
25.63 + <td><button id="n4">4</button></td>
25.64 + <td><button id="n5">5</button></td>
25.65 + <td><button id="n6">6</button></td>
25.66 + <td><button id="minus">-</button></td>
25.67 + </tr>
25.68 + <tr>
25.69 + <td><button id="n7">7</button></td>
25.70 + <td><button id="n8">8</button></td>
25.71 + <td><button id="n9">9</button></td>
25.72 + <td><button id="mul">*</button></td>
25.73 + </tr>
25.74 + <tr>
25.75 + <td><button id="clear">C</button></td>
25.76 + <td><button id="n0">0</button></td>
25.77 + <td><button id="result">=</button></td>
25.78 + <td><button id="div">/</button></td>
25.79 + </tr>
25.80 + </tbody>
25.81 + </table>
25.82 + <div data-bind="text: displayPreview"></div>
25.83 + <script src="bootjava.js"/>
25.84 +
25.85 + <hr/>
25.86 + <pre>
25.87 + <span class="keyword-directive">package</span> org.apidesign.bck2brwsr.mavenhtml;
25.88 +
25.89 + <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.OnClick;
25.90 + <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.Page;
25.91 +
25.92 + <span class="comment">/**</span> <span class="comment">HTML5</span><span class="comment"> & </span><span class="comment">Java</span> <span class="comment">demo</span> <span class="comment">showing</span> <span class="comment">the</span> <span class="comment">power</span> <span class="comment">of</span> <a href="http://wiki.apidesign.org/wiki/AnnotationProcessor">annotation processors</a>
25.93 + <span class="comment"> * </span><span class="comment">as</span> <span class="comment">well</span> <span class="comment">as</span> <span class="comment">other</span> <span class="comment">goodies</span><span class="comment">, including type-safe association between</span>
25.94 + <span class="comment"> * </span><span class="comment">an XHTML page and Java.</span>
25.95 + <span class="comment"> * </span>
25.96 + <span class="comment"> * </span><span class="ST1">@author</span> <span class="comment">Jaroslav</span> <span class="comment">Tulach</span> <span class="ST0"><jaroslav.tulach@apidesign.org></span>
25.97 + <span class="comment">*/</span>
25.98 + @Page(xhtml=<span class="string">"</span><span class="string">Calculator.xhtml</span><span class="string">"</span>)
25.99 + <span class="keyword-directive">public</span> <span class="keyword-directive">class</span> App {
25.100 + <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> memory;
25.101 + <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> String operation;
25.102 +
25.103 + @OnClick(id=<span class="string">"</span><span class="string">clear</span><span class="string">"</span>)
25.104 + <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> clear() {
25.105 + memory = <span class="number">0</span>;
25.106 + operation = <span class="keyword-directive">null</span>;
25.107 + Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">0</span><span class="string">"</span>);
25.108 + }
25.109 +
25.110 + @OnClick(id= { <span class="string">"</span><span class="string">plus</span><span class="string">"</span>, <span class="string">"</span><span class="string">minus</span><span class="string">"</span>, <span class="string">"</span><span class="string">mul</span><span class="string">"</span>, <span class="string">"</span><span class="string">div</span><span class="string">"</span> })
25.111 + <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> applyOp(String op) {
25.112 + memory = getValue();
25.113 + operation = op;
25.114 + Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">0</span><span class="string">"</span>);
25.115 + }
25.116 +
25.117 + @OnClick(id=<span class="string">"</span><span class="string">result</span><span class="string">"</span>)
25.118 + <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> computeTheValue() {
25.119 + <span class="keyword-directive">switch</span> (operation) {
25.120 + <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">plus</span><span class="string">"</span>: setValue(memory + getValue()); <span class="keyword-directive">break</span>;
25.121 + <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">minus</span><span class="string">"</span>: setValue(memory - getValue()); <span class="keyword-directive">break</span>;
25.122 + <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">mul</span><span class="string">"</span>: setValue(memory * getValue()); <span class="keyword-directive">break</span>;
25.123 + <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">div</span><span class="string">"</span>: setValue(memory / getValue()); <span class="keyword-directive">break</span>;
25.124 + <span class="keyword-directive">default</span>: <span class="keyword-directive">throw</span> <span class="keyword-directive">new</span> IllegalStateException(operation);
25.125 + }
25.126 + }
25.127 +
25.128 + @OnClick(id={<span class="string">"</span><span class="string">n0</span><span class="string">"</span>, <span class="string">"</span><span class="string">n1</span><span class="string">"</span>, <span class="string">"</span><span class="string">n2</span><span class="string">"</span>, <span class="string">"</span><span class="string">n3</span><span class="string">"</span>, <span class="string">"</span><span class="string">n4</span><span class="string">"</span>, <span class="string">"</span><span class="string">n5</span><span class="string">"</span>, <span class="string">"</span><span class="string">n6</span><span class="string">"</span>, <span class="string">"</span><span class="string">n7</span><span class="string">"</span>, <span class="string">"</span><span class="string">n8</span><span class="string">"</span>, <span class="string">"</span><span class="string">n9</span><span class="string">"</span>})
25.129 + <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> addDigit(String digit) {
25.130 + digit = digit.substring(<span class="number">1</span>);
25.131 + String v = Calculator.DISPLAY.getValue();
25.132 + <span class="keyword-directive">if</span> (getValue() == <span class="number">0.0</span>) {
25.133 + Calculator.DISPLAY.setValue(digit);
25.134 + } <span class="keyword-directive">else</span> {
25.135 + Calculator.DISPLAY.setValue(v + digit);
25.136 + }
25.137 + }
25.138 +
25.139 + <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> setValue(<span class="keyword-directive">double</span> v) {
25.140 + StringBuilder sb = <span class="keyword-directive">new</span> StringBuilder();
25.141 + sb.append(v);
25.142 + Calculator.DISPLAY.setValue(sb.toString());
25.143 + }
25.144 +
25.145 + <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> getValue() {
25.146 + <span class="keyword-directive">try</span> {
25.147 + <span class="keyword-directive">return</span> Double.parseDouble(Calculator.DISPLAY.getValue());
25.148 + } <span class="keyword-directive">catch</span> (NumberFormatException ex) {
25.149 + Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">err</span><span class="string">"</span>);
25.150 + <span class="keyword-directive">return</span> <span class="number">0.0</span>;
25.151 + }
25.152 + }
25.153 + }
25.154 +
25.155 + </pre>
25.156 + </body>
25.157 +</html>
26.1 --- a/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/mavenhtml/Calculator.xhtml Tue Jan 22 19:21:34 2013 +0100
26.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
26.3 @@ -1,154 +0,0 @@
26.4 -<?xml version="1.0" encoding="UTF-8"?>
26.5 -<!--
26.6 -
26.7 - Back 2 Browser Bytecode Translator
26.8 - Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
26.9 -
26.10 - This program is free software: you can redistribute it and/or modify
26.11 - it under the terms of the GNU General Public License as published by
26.12 - the Free Software Foundation, version 2 of the License.
26.13 -
26.14 - This program is distributed in the hope that it will be useful,
26.15 - but WITHOUT ANY WARRANTY; without even the implied warranty of
26.16 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26.17 - GNU General Public License for more details.
26.18 -
26.19 - You should have received a copy of the GNU General Public License
26.20 - along with this program. Look for COPYING file in the top folder.
26.21 - If not, see http://opensource.org/licenses/GPL-2.0.
26.22 -
26.23 --->
26.24 -<!DOCTYPE html>
26.25 -<html xmlns="http://www.w3.org/1999/xhtml">
26.26 - <head>
26.27 - <title>Simple Calculator in HTML5 and Java</title>
26.28 -
26.29 - <style type="text/css">
26.30 - body {color: #ffffff; background-color: #121e31; font-family: Monospaced}
26.31 - pre {color: #ffffff; background-color: #121e31; font-family: Monospaced}
26.32 - table {color: #ffffff; background-color: #121e31; font-family: Monospaced}
26.33 - .string {color: #e2ce00}
26.34 - a {color: #e2ce00}
26.35 - .ST1 {color: #0000cc; font-family: Monospaced; font-weight: bold}
26.36 - .ST0 {color: #0000ff}
26.37 - .comment {color: #428bdd}
26.38 - .keyword-directive {color: #f8bb00}
26.39 - .tag {color: #f8bb00}
26.40 - .ST0 {color: #628fb5; background-color: #1b3450}
26.41 - .sgml-comment {color: #808080}
26.42 - .value {color: #99006b}
26.43 - .argument {color: #007c00}
26.44 - .sgml-declaration {color: #bf9221}
26.45 - </style>
26.46 - </head>
26.47 - <body>
26.48 - <h1>Java and HTML5 - Together at Last!</h1>
26.49 - <table border="0" cellspacing="2">
26.50 - <tbody>
26.51 - <tr>
26.52 - <td colspan="4"><input id="display" value="0"
26.53 - style="text-align: right"/>
26.54 - </td>
26.55 - </tr>
26.56 - <tr>
26.57 - <td><button id="n1">1</button></td>
26.58 - <td><button id="n2">2</button></td>
26.59 - <td><button id="n3">3</button></td>
26.60 - <td><button id="plus">+</button></td>
26.61 - </tr>
26.62 - <tr>
26.63 - <td><button id="n4">4</button></td>
26.64 - <td><button id="n5">5</button></td>
26.65 - <td><button id="n6">6</button></td>
26.66 - <td><button id="minus">-</button></td>
26.67 - </tr>
26.68 - <tr>
26.69 - <td><button id="n7">7</button></td>
26.70 - <td><button id="n8">8</button></td>
26.71 - <td><button id="n9">9</button></td>
26.72 - <td><button id="mul">*</button></td>
26.73 - </tr>
26.74 - <tr>
26.75 - <td><button id="clear">C</button></td>
26.76 - <td><button id="n0">0</button></td>
26.77 - <td><button id="result">=</button></td>
26.78 - <td><button id="div">/</button></td>
26.79 - </tr>
26.80 - </tbody>
26.81 - </table>
26.82 -
26.83 - <script src="bootjava.js"/>
26.84 -
26.85 - <hr/>
26.86 - <pre>
26.87 - <span class="keyword-directive">package</span> org.apidesign.bck2brwsr.mavenhtml;
26.88 -
26.89 - <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.OnClick;
26.90 - <span class="keyword-directive">import</span> org.apidesign.bck2brwsr.htmlpage.api.Page;
26.91 -
26.92 - <span class="comment">/**</span> <span class="comment">HTML5</span><span class="comment"> & </span><span class="comment">Java</span> <span class="comment">demo</span> <span class="comment">showing</span> <span class="comment">the</span> <span class="comment">power</span> <span class="comment">of</span> <a href="http://wiki.apidesign.org/wiki/AnnotationProcessor">annotation processors</a>
26.93 - <span class="comment"> * </span><span class="comment">as</span> <span class="comment">well</span> <span class="comment">as</span> <span class="comment">other</span> <span class="comment">goodies</span><span class="comment">, including type-safe association between</span>
26.94 - <span class="comment"> * </span><span class="comment">an XHTML page and Java.</span>
26.95 - <span class="comment"> * </span>
26.96 - <span class="comment"> * </span><span class="ST1">@author</span> <span class="comment">Jaroslav</span> <span class="comment">Tulach</span> <span class="ST0"><jaroslav.tulach@apidesign.org></span>
26.97 - <span class="comment">*/</span>
26.98 - @Page(xhtml=<span class="string">"</span><span class="string">Calculator.xhtml</span><span class="string">"</span>)
26.99 - <span class="keyword-directive">public</span> <span class="keyword-directive">class</span> App {
26.100 - <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> memory;
26.101 - <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> String operation;
26.102 -
26.103 - @OnClick(id=<span class="string">"</span><span class="string">clear</span><span class="string">"</span>)
26.104 - <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> clear() {
26.105 - memory = <span class="number">0</span>;
26.106 - operation = <span class="keyword-directive">null</span>;
26.107 - Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">0</span><span class="string">"</span>);
26.108 - }
26.109 -
26.110 - @OnClick(id= { <span class="string">"</span><span class="string">plus</span><span class="string">"</span>, <span class="string">"</span><span class="string">minus</span><span class="string">"</span>, <span class="string">"</span><span class="string">mul</span><span class="string">"</span>, <span class="string">"</span><span class="string">div</span><span class="string">"</span> })
26.111 - <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> applyOp(String op) {
26.112 - memory = getValue();
26.113 - operation = op;
26.114 - Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">0</span><span class="string">"</span>);
26.115 - }
26.116 -
26.117 - @OnClick(id=<span class="string">"</span><span class="string">result</span><span class="string">"</span>)
26.118 - <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> computeTheValue() {
26.119 - <span class="keyword-directive">switch</span> (operation) {
26.120 - <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">plus</span><span class="string">"</span>: setValue(memory + getValue()); <span class="keyword-directive">break</span>;
26.121 - <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">minus</span><span class="string">"</span>: setValue(memory - getValue()); <span class="keyword-directive">break</span>;
26.122 - <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">mul</span><span class="string">"</span>: setValue(memory * getValue()); <span class="keyword-directive">break</span>;
26.123 - <span class="keyword-directive">case</span> <span class="string">"</span><span class="string">div</span><span class="string">"</span>: setValue(memory / getValue()); <span class="keyword-directive">break</span>;
26.124 - <span class="keyword-directive">default</span>: <span class="keyword-directive">throw</span> <span class="keyword-directive">new</span> IllegalStateException(operation);
26.125 - }
26.126 - }
26.127 -
26.128 - @OnClick(id={<span class="string">"</span><span class="string">n0</span><span class="string">"</span>, <span class="string">"</span><span class="string">n1</span><span class="string">"</span>, <span class="string">"</span><span class="string">n2</span><span class="string">"</span>, <span class="string">"</span><span class="string">n3</span><span class="string">"</span>, <span class="string">"</span><span class="string">n4</span><span class="string">"</span>, <span class="string">"</span><span class="string">n5</span><span class="string">"</span>, <span class="string">"</span><span class="string">n6</span><span class="string">"</span>, <span class="string">"</span><span class="string">n7</span><span class="string">"</span>, <span class="string">"</span><span class="string">n8</span><span class="string">"</span>, <span class="string">"</span><span class="string">n9</span><span class="string">"</span>})
26.129 - <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> addDigit(String digit) {
26.130 - digit = digit.substring(<span class="number">1</span>);
26.131 - String v = Calculator.DISPLAY.getValue();
26.132 - <span class="keyword-directive">if</span> (getValue() == <span class="number">0.0</span>) {
26.133 - Calculator.DISPLAY.setValue(digit);
26.134 - } <span class="keyword-directive">else</span> {
26.135 - Calculator.DISPLAY.setValue(v + digit);
26.136 - }
26.137 - }
26.138 -
26.139 - <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">void</span> setValue(<span class="keyword-directive">double</span> v) {
26.140 - StringBuilder sb = <span class="keyword-directive">new</span> StringBuilder();
26.141 - sb.append(v);
26.142 - Calculator.DISPLAY.setValue(sb.toString());
26.143 - }
26.144 -
26.145 - <span class="keyword-directive">private</span> <span class="keyword-directive">static</span> <span class="keyword-directive">double</span> getValue() {
26.146 - <span class="keyword-directive">try</span> {
26.147 - <span class="keyword-directive">return</span> Double.parseDouble(Calculator.DISPLAY.getValue());
26.148 - } <span class="keyword-directive">catch</span> (NumberFormatException ex) {
26.149 - Calculator.DISPLAY.setValue(<span class="string">"</span><span class="string">err</span><span class="string">"</span>);
26.150 - <span class="keyword-directive">return</span> <span class="number">0.0</span>;
26.151 - }
26.152 - }
26.153 - }
26.154 -
26.155 - </pre>
26.156 - </body>
26.157 -</html>
27.1 --- a/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Tue Jan 22 19:21:34 2013 +0100
27.2 +++ b/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Wed Jan 23 10:57:05 2013 +0100
27.3 @@ -261,6 +261,11 @@
27.4 LOG.log(Level.INFO, "Showing {0}", uri);
27.5 if (cmd == null) {
27.6 try {
27.7 + LOG.log(Level.INFO, "Trying Desktop.browse on {0} {2} by {1}", new Object[] {
27.8 + System.getProperty("java.vm.name"),
27.9 + System.getProperty("java.vm.vendor"),
27.10 + System.getProperty("java.vm.version"),
27.11 + });
27.12 java.awt.Desktop.getDesktop().browse(uri);
27.13 LOG.log(Level.INFO, "Desktop.browse successfully finished");
27.14 return null;
28.1 --- a/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Console.java Tue Jan 22 19:21:34 2013 +0100
28.2 +++ b/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Console.java Wed Jan 23 10:57:05 2013 +0100
28.3 @@ -200,7 +200,7 @@
28.4 } else {
28.5 res = found.invoke(c.newInstance());
28.6 }
28.7 - } catch (Exception | Error ex) {
28.8 + } catch (Throwable ex) {
28.9 res = ex.getClass().getName() + ":" + ex.getMessage();
28.10 }
28.11 } else {
29.1 --- a/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrswrMojo.java Tue Jan 22 19:21:34 2013 +0100
29.2 +++ b/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrswrMojo.java Wed Jan 23 10:57:05 2013 +0100
29.3 @@ -82,7 +82,10 @@
29.4 List<URL> arr = new ArrayList<URL>();
29.5 arr.add(root.toURI().toURL());
29.6 for (Artifact a : deps) {
29.7 - arr.add(a.getFile().toURI().toURL());
29.8 + final File f = a.getFile();
29.9 + if (f != null) {
29.10 + arr.add(f.toURI().toURL());
29.11 + }
29.12 }
29.13 return new URLClassLoader(arr.toArray(new URL[0]), BrswrMojo.class.getClassLoader());
29.14 }
30.1 --- a/mojo/src/main/resources/META-INF/maven/archetype-metadata.xml Tue Jan 22 19:21:34 2013 +0100
30.2 +++ b/mojo/src/main/resources/META-INF/maven/archetype-metadata.xml Wed Jan 23 10:57:05 2013 +0100
30.3 @@ -23,7 +23,7 @@
30.4 <fileSet filtered="true" packaged="true">
30.5 <directory>src/main/java</directory>
30.6 <includes>
30.7 - <include>**/*.java</include>
30.8 + <include>**/App.java</include>
30.9 </includes>
30.10 </fileSet>
30.11 <fileSet filtered="true" packaged="true">
30.12 @@ -32,6 +32,12 @@
30.13 <include>**/*.xhtml</include>
30.14 </includes>
30.15 </fileSet>
30.16 + <fileSet filtered="true" packaged="true">
30.17 + <directory>src/test/java</directory>
30.18 + <includes>
30.19 + <include>**/*Test.java</include>
30.20 + </includes>
30.21 + </fileSet>
30.22 <fileSet filtered="false" packaged="false">
30.23 <directory></directory>
30.24 <includes>
31.1 --- a/mojo/src/main/resources/archetype-resources/pom.xml Tue Jan 22 19:21:34 2013 +0100
31.2 +++ b/mojo/src/main/resources/archetype-resources/pom.xml Wed Jan 23 10:57:05 2013 +0100
31.3 @@ -53,5 +53,17 @@
31.4 <artifactId>javaquery.api</artifactId>
31.5 <version>0.3-SNAPSHOT</version>
31.6 </dependency>
31.7 + <dependency>
31.8 + <groupId>org.testng</groupId>
31.9 + <artifactId>testng</artifactId>
31.10 + <version>6.5.2</version>
31.11 + <scope>test</scope>
31.12 + </dependency>
31.13 + <dependency>
31.14 + <groupId>${project.groupId}</groupId>
31.15 + <artifactId>vmtest</artifactId>
31.16 + <version>0.3-SNAPSHOT</version>
31.17 + <scope>test</scope>
31.18 + </dependency>
31.19 </dependencies>
31.20 </project>
32.1 --- a/mojo/src/main/resources/archetype-resources/src/main/java/App.java Tue Jan 22 19:21:34 2013 +0100
32.2 +++ b/mojo/src/main/resources/archetype-resources/src/main/java/App.java Wed Jan 23 10:57:05 2013 +0100
32.3 @@ -3,15 +3,32 @@
32.4 import org.apidesign.bck2brwsr.htmlpage.api.*;
32.5 import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*;
32.6 import org.apidesign.bck2brwsr.htmlpage.api.Page;
32.7 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
32.8 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
32.9
32.10 /** Edit the index.xhtml file. Use 'id' to name certain HTML elements.
32.11 * Use this class to define behavior of the elements.
32.12 */
32.13 -@Page(xhtml="index.xhtml", className="Index")
32.14 +@Page(xhtml="index.xhtml", className="Index", properties={
32.15 + @Property(name="name", type=String.class)
32.16 +})
32.17 public class App {
32.18 + static {
32.19 + Index model = new Index();
32.20 + model.setName("World");
32.21 + model.applyBindings();
32.22 + }
32.23 +
32.24 @On(event = CLICK, id="hello")
32.25 - static void hello() {
32.26 - Index.HELLO.setDisabled(true);
32.27 - Element.alert("Hello World!");
32.28 + static void hello(Index m) {
32.29 + GraphicsContext g = m.CANVAS.getContext();
32.30 + g.clearRect(0, 0, 1000, 1000);
32.31 + g.setFont("italic 40px Calibri");
32.32 + g.fillText(m.getHelloMessage(), 10, 40);
32.33 + }
32.34 +
32.35 + @ComputedProperty
32.36 + static String helloMessage(String name) {
32.37 + return "Hello " + name + "!";
32.38 }
32.39 }
33.1 --- a/mojo/src/main/resources/archetype-resources/src/main/resources/index.xhtml Tue Jan 22 19:21:34 2013 +0100
33.2 +++ b/mojo/src/main/resources/archetype-resources/src/main/resources/index.xhtml Wed Jan 23 10:57:05 2013 +0100
33.3 @@ -5,12 +5,18 @@
33.4 <title>Bck2Brwsr's Hello World</title>
33.5 </head>
33.6 <body>
33.7 - <button id="hello">Hello World!</button>
33.8 + <h1 data-bind="text: helloMessage">Loading Bck2Brwsr's Hello World...</h1>
33.9 + Your name: <input id='input' data-bind="value: name, valueUpdate: 'afterkeydown'"></input>
33.10 + <button id="hello">Say Hello!</button>
33.11 + <p>
33.12 + <canvas id="canvas" width="300" height="50">
33.13 + </canvas>
33.14 + </p>
33.15
33.16 <script src="/bck2brwsr.js"></script>
33.17 <script src="/vm.js"></script>
33.18 <script type="text/javascript">
33.19 - vm.loadClass('${package}.Index');
33.20 + vm.loadClass('${package}.App');
33.21 </script>
33.22 </body>
33.23 </html>
34.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
34.2 +++ b/mojo/src/main/resources/archetype-resources/src/test/java/AppTest.java Wed Jan 23 10:57:05 2013 +0100
34.3 @@ -0,0 +1,26 @@
34.4 +package ${package};
34.5 +
34.6 +import static org.testng.Assert.*;
34.7 +import org.testng.annotations.BeforeMethod;
34.8 +import org.testng.annotations.Test;
34.9 +
34.10 +/** Demonstrating POJO testing of HTML page model. Runs in good old HotSpot
34.11 + * as it does not reference any HTML elements or browser functionality. Just
34.12 + * operates on the page model.
34.13 + *
34.14 + * @author Jaroslav Tulach <jtulach@netbeans.org>
34.15 + */
34.16 +public class AppTest {
34.17 + private Index model;
34.18 +
34.19 +
34.20 + @BeforeMethod
34.21 + public void initModel() {
34.22 + model = new Index().applyBindings();
34.23 + }
34.24 +
34.25 + @Test public void testHelloMessage() {
34.26 + model.setName("Joe");
34.27 + assertEquals(model.getHelloMessage(), "Hello Joe!", "Cleared after pressing +");
34.28 + }
34.29 +}
35.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
35.2 +++ b/mojo/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java Wed Jan 23 10:57:05 2013 +0100
35.3 @@ -0,0 +1,40 @@
35.4 +package ${package};
35.5 +
35.6 +import org.apidesign.bck2brwsr.vmtest.Compare;
35.7 +import org.apidesign.bck2brwsr.vmtest.VMTest;
35.8 +import org.testng.annotations.Factory;
35.9 +
35.10 +/** Bck2brwsr cares about compatibility with real Java. Whatever API is
35.11 + * supported by bck2brwsr, it needs to behave the same way as when running
35.12 + * in HotSpot VM.
35.13 + * <p>
35.14 + * There can be bugs, however. To help us fix them, we kindly ask you to
35.15 + * write an "inconsistency" test. A test that compares behavior of the API
35.16 + * between real VM and bck2brwsr VM. This class is skeleton of such test.
35.17 + *
35.18 + * @author Jaroslav Tulach <jtulach@netbeans.org>
35.19 + */
35.20 +public class InconsistencyTest {
35.21 + /** A method to demonstrate inconsistency between bck2brwsr and HotSpot.
35.22 + * Make calls to an API that behaves strangely, return some result at
35.23 + * the end. No need to use any <code>assert</code>.
35.24 + *
35.25 + * @return value to compare between HotSpot and bck2brwsr
35.26 + */
35.27 + @Compare
35.28 + public int checkStringHashCode() throws Exception {
35.29 + return "Is string hashCode the same?".hashCode();
35.30 + }
35.31 +
35.32 + /** Factory method that creates a three tests for each method annotated with
35.33 + * {@link org.apidesign.bck2brwsr.vmtest.Compare}. One executes the code in
35.34 + * HotSpot, one in Rhino and the last one compares the results.
35.35 + *
35.36 + * @see org.apidesign.bck2brwsr.vmtest.VMTest
35.37 + */
35.38 + @Factory
35.39 + public static Object[] create() {
35.40 + return VMTest.create(InconsistencyTest.class);
35.41 + }
35.42 +
35.43 +}
36.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
36.2 +++ b/mojo/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java Wed Jan 23 10:57:05 2013 +0100
36.3 @@ -0,0 +1,46 @@
36.4 +package ${package};
36.5 +
36.6 +import org.apidesign.bck2brwsr.htmlpage.api.OnEvent;
36.7 +import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
36.8 +import org.apidesign.bck2brwsr.vmtest.HtmlFragment;
36.9 +import org.apidesign.bck2brwsr.vmtest.VMTest;
36.10 +import org.testng.annotations.Factory;
36.11 +
36.12 +/** Sometimes it is useful to run tests inside of the real browser.
36.13 + * To do that just annotate your method with {@link org.apidesign.bck2brwsr.vmtest.BrwsrTest}
36.14 + * and that is it. If your code references elements on the HTML page,
36.15 + * you can pass in an {@link org.apidesign.bck2brwsr.vmtest.HtmlFragment} which
36.16 + * will be made available on the page before your test starts.
36.17 + *
36.18 + * @author Jaroslav Tulach <jtulach@netbeans.org>
36.19 + */
36.20 +public class IntegrationTest {
36.21 +
36.22 + /** Write to testing code here. Use <code>assert</code> (but not TestNG's
36.23 + * Assert, as TestNG is not compiled with target 1.6 yet).
36.24 + */
36.25 + @HtmlFragment(
36.26 + "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
36.27 + "Your name: <input id='input' data-bind=\"value: name, valueUpdate: 'afterkeydown'\"></input>\n" +
36.28 + "<button id=\"hello\">Say Hello!</button>\n" +
36.29 + "<p>\n" +
36.30 + " <canvas id=\"canvas\" width=\"300\" height=\"50\"></canvas>\n" +
36.31 + "</p>\n"
36.32 + )
36.33 + @BrwsrTest
36.34 + public void modifyValueAssertChangeInModel() {
36.35 + Index m = new Index();
36.36 + m.setName("Joe Hacker");
36.37 + m.applyBindings();
36.38 + assert "Joe Hacker".equals(m.INPUT.getValue()) : "Value is really Joe Hacker: " + m.INPUT.getValue();
36.39 + m.INPUT.setValue("Happy Joe");
36.40 + m.triggerEvent(m.INPUT, OnEvent.CHANGE);
36.41 + assert "Happy Joe".equals(m.getName()) : "Name property updated to Happy Joe: " + m.getName();
36.42 + }
36.43 +
36.44 + @Factory
36.45 + public static Object[] create() {
36.46 + return VMTest.create(IntegrationTest.class);
36.47 + }
36.48 +
36.49 +}
37.1 --- a/pom.xml Tue Jan 22 19:21:34 2013 +0100
37.2 +++ b/pom.xml Wed Jan 23 10:57:05 2013 +0100
37.3 @@ -75,6 +75,7 @@
37.4 <exclude>.*/**</exclude>
37.5 <exclude>mojo/src/main/resources/archetype-resources/**</exclude>
37.6 <exclude>vmtest/src/test/resources/**</exclude>
37.7 + <exclude>javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout*.js</exclude>
37.8 </excludes>
37.9 </configuration>
37.10 </plugin>
37.11 @@ -112,4 +113,4 @@
37.12 <properties>
37.13 <license>COPYING</license>
37.14 </properties>
37.15 -</project>
37.16 \ No newline at end of file
37.17 +</project>
38.1 --- a/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Tue Jan 22 19:21:34 2013 +0100
38.2 +++ b/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Wed Jan 23 10:57:05 2013 +0100
38.3 @@ -212,7 +212,7 @@
38.4 out.append("\n return this;");
38.5 out.append("\n }");
38.6 out.append("\n return arguments[0] ? new CLS() : CLS.prototype;");
38.7 - out.append("\n}");
38.8 + out.append("\n};");
38.9 StringBuilder sb = new StringBuilder();
38.10 for (String init : toInitilize.toArray()) {
38.11 sb.append("\n").append(init).append("();");
39.1 --- a/vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java Tue Jan 22 19:21:34 2013 +0100
39.2 +++ b/vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java Wed Jan 23 10:57:05 2013 +0100
39.3 @@ -45,7 +45,9 @@
39.4 sb.append("\n return c[method]();");
39.5 sb.append("\n}");
39.6
39.7 -
39.8 + sb.append("\nfunction checkKO() {");
39.9 + sb.append("\n return ko !== null;");
39.10 + sb.append("\n}");
39.11
39.12 ScriptEngine[] arr = { null };
39.13 code = StaticMethodTest.compileClass(sb, arr,
39.14 @@ -71,6 +73,9 @@
39.15 assertExec("ko is defined", "test", true,
39.16 Script.class.getName(), "checkNotNull__Z"
39.17 );
39.18 +
39.19 + Object res = code.invokeFunction("checkKO");
39.20 + assertEquals(res, true, "KO is defined on a global level");
39.21 }
39.22
39.23 private static void assertExec(String msg, String methodName, Object expRes, Object... args) throws Exception {
40.1 --- a/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Compare.java Tue Jan 22 19:21:34 2013 +0100
40.2 +++ b/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Compare.java Wed Jan 23 10:57:05 2013 +0100
40.3 @@ -24,7 +24,7 @@
40.4
40.5 /** Can be applied on a method that yields a return value.
40.6 * Together with {@link VMTest#create} it can be used to write
40.7 - * methods which are executed in real as well as JavaScript VMs and
40.8 + * methods which are executed in real VM as well as JavaScript VMs and
40.9 * their results are compared.
40.10 *
40.11 * @author Jaroslav Tulach <jtulach@netbeans.org>
40.12 @@ -32,4 +32,14 @@
40.13 @Retention(RetentionPolicy.RUNTIME)
40.14 @Target(ElementType.METHOD)
40.15 public @interface Compare {
40.16 + /** Specifies whether the system should internal JavaScript interpreter
40.17 + * as available via {@link javax.script.ScriptEngine}. Defaults to true,
40.18 + * but in some situations (benchmarking comes to my mind), one may set this
40.19 + * to <code>false</code>. In such case only browsers provided via
40.20 + * <code>vmtest.brwsrs</code> property are used. For example
40.21 + * <code>"vmtest.brwsrs=firefox,google-chrome"</code> would run the test
40.22 + * in HotSpot VM, firefox and chrome and would compare the results.
40.23 + * @return
40.24 + */
40.25 + boolean scripting() default true;
40.26 }
41.1 --- a/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Tue Jan 22 19:21:34 2013 +0100
41.2 +++ b/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Wed Jan 23 10:57:05 2013 +0100
41.3 @@ -112,10 +112,12 @@
41.4 return;
41.5 }
41.6 final Bck2BrwsrCase real = new Bck2BrwsrCase(m, "Java", null, false, null);
41.7 - final Bck2BrwsrCase js = new Bck2BrwsrCase(m, "JavaScript", l.javaScript(), false, null);
41.8 ret.add(real);
41.9 - ret.add(js);
41.10 - ret.add(new CompareCase(m, real, js));
41.11 + if (c.scripting()) {
41.12 + final Bck2BrwsrCase js = new Bck2BrwsrCase(m, "JavaScript", l.javaScript(), false, null);
41.13 + ret.add(js);
41.14 + ret.add(new CompareCase(m, real, js));
41.15 + }
41.16 for (String b : brwsr) {
41.17 final Launcher s = l.brwsr(b);
41.18 ret.add(s);