1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/ko-archetype-test/pom.xml Mon Jun 24 17:49:27 2013 +0200
1.3 @@ -0,0 +1,48 @@
1.4 +<?xml version="1.0"?>
1.5 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
1.6 + <modelVersion>4.0.0</modelVersion>
1.7 + <parent>
1.8 + <groupId>org.apidesign</groupId>
1.9 + <artifactId>html</artifactId>
1.10 + <version>0.4-SNAPSHOT</version>
1.11 + </parent>
1.12 + <groupId>org.apidesign.html</groupId>
1.13 + <artifactId>ko-archetype-test</artifactId>
1.14 + <version>0.4-SNAPSHOT</version>
1.15 + <name>Knockout 4 Java Archetype Test</name>
1.16 + <url>http://maven.apache.org</url>
1.17 + <description>Verifies the Knockout & net.java.html.json archetype behaves properly.</description>
1.18 + <properties>
1.19 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1.20 + </properties>
1.21 + <dependencies>
1.22 + <dependency>
1.23 + <groupId>${project.groupId}</groupId>
1.24 + <artifactId>knockout4j-archetype</artifactId>
1.25 + <version>0.4-SNAPSHOT</version>
1.26 + </dependency>
1.27 + <dependency>
1.28 + <groupId>org.testng</groupId>
1.29 + <artifactId>testng</artifactId>
1.30 + <scope>test</scope>
1.31 + </dependency>
1.32 + <dependency>
1.33 + <groupId>org.apache.maven.shared</groupId>
1.34 + <artifactId>maven-verifier</artifactId>
1.35 + <version>1.4</version>
1.36 + <scope>test</scope>
1.37 + </dependency>
1.38 + <dependency>
1.39 + <groupId>${project.groupId}</groupId>
1.40 + <artifactId>ko-fx</artifactId>
1.41 + <version>${project.version}</version>
1.42 + <scope>provided</scope>
1.43 + </dependency>
1.44 + <dependency>
1.45 + <groupId>${project.groupId}</groupId>
1.46 + <artifactId>ko-bck2brwsr</artifactId>
1.47 + <version>${project.version}</version>
1.48 + <scope>provided</scope>
1.49 + </dependency>
1.50 + </dependencies>
1.51 +</project>
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/ko-archetype-test/src/test/java/org/apidesign/html/archetype/test/ArchetypeVersionTest.java Mon Jun 24 17:49:27 2013 +0200
2.3 @@ -0,0 +1,139 @@
2.4 +/**
2.5 + * HTML via Java(tm) Language Bindings
2.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
2.7 + *
2.8 + * This program is free software: you can redistribute it and/or modify
2.9 + * it under the terms of the GNU General Public License as published by
2.10 + * the Free Software Foundation, version 2 of the License.
2.11 + *
2.12 + * This program is distributed in the hope that it will be useful,
2.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2.15 + * GNU General Public License for more details. apidesign.org
2.16 + * designates this particular file as subject to the
2.17 + * "Classpath" exception as provided by apidesign.org
2.18 + * in the License file that accompanied this code.
2.19 + *
2.20 + * You should have received a copy of the GNU General Public License
2.21 + * along with this program. Look for COPYING file in the top folder.
2.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
2.23 + */
2.24 +package org.apidesign.html.archetype.test;
2.25 +
2.26 +import java.io.IOException;
2.27 +import java.net.URL;
2.28 +import javax.xml.XMLConstants;
2.29 +import javax.xml.parsers.DocumentBuilderFactory;
2.30 +import javax.xml.parsers.ParserConfigurationException;
2.31 +import javax.xml.xpath.XPathConstants;
2.32 +import javax.xml.xpath.XPathExpression;
2.33 +import javax.xml.xpath.XPathExpressionException;
2.34 +import javax.xml.xpath.XPathFactory;
2.35 +import javax.xml.xpath.XPathFactoryConfigurationException;
2.36 +import org.testng.annotations.Test;
2.37 +import static org.testng.Assert.*;
2.38 +import org.testng.annotations.BeforeClass;
2.39 +import org.w3c.dom.Document;
2.40 +import org.w3c.dom.NodeList;
2.41 +import org.xml.sax.SAXException;
2.42 +
2.43 +/**
2.44 + *
2.45 + * @author Jaroslav Tulach <jtulach@netbeans.org>
2.46 + */
2.47 +public class ArchetypeVersionTest {
2.48 + private String version;
2.49 +
2.50 + public ArchetypeVersionTest() {
2.51 + }
2.52 +
2.53 + @BeforeClass public void readCurrentVersion() throws Exception {
2.54 + version = findCurrentVersion();
2.55 + assertFalse(version.isEmpty(), "There should be some version string");
2.56 + }
2.57 +
2.58 +
2.59 + @Test public void testComparePomDepsVersions() throws Exception {
2.60 + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader();
2.61 + URL r = l.getResource("archetype-resources/pom.xml");
2.62 + assertNotNull(r, "Archetype pom found");
2.63 +
2.64 + final XPathFactory fact = XPathFactory.newInstance();
2.65 + XPathExpression xp2 = fact.newXPath().compile(
2.66 + "//properties/net.java.html.version/text()"
2.67 + );
2.68 +
2.69 + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream());
2.70 + String arch = (String) xp2.evaluate(dom, XPathConstants.STRING);
2.71 +
2.72 + assertEquals(arch, version, "net.java.html.json dependency needs to be on latest version");
2.73 + }
2.74 +
2.75 + @Test public void testCheckLauncher() throws Exception {
2.76 + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader();
2.77 + URL r = l.getResource("archetype-resources/pom.xml");
2.78 + assertNotNull(r, "Archetype pom found");
2.79 +
2.80 + final XPathFactory fact = XPathFactory.newInstance();
2.81 + XPathExpression xp2 = fact.newXPath().compile(
2.82 + "//properties/bck2brwsr.launcher.version/text()"
2.83 + );
2.84 +
2.85 + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream());
2.86 + String arch = (String) xp2.evaluate(dom, XPathConstants.STRING);
2.87 +
2.88 +
2.89 + assertTrue(arch.matches("[0-9\\.]+"), "launcher version seems valid: " + arch);
2.90 + }
2.91 +
2.92 + @Test public void testCheckBck2Brwsr() throws Exception {
2.93 + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader();
2.94 + URL r = l.getResource("archetype-resources/pom.xml");
2.95 + assertNotNull(r, "Archetype pom found");
2.96 +
2.97 + final XPathFactory fact = XPathFactory.newInstance();
2.98 + XPathExpression xp2 = fact.newXPath().compile(
2.99 + "//properties/bck2brwsr.version/text()"
2.100 + );
2.101 +
2.102 + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream());
2.103 + String arch = (String) xp2.evaluate(dom, XPathConstants.STRING);
2.104 +
2.105 + assertTrue(arch.matches("[0-9\\.]+"), "bck2brwsr version seems valid: " + arch);
2.106 + }
2.107 +
2.108 + @Test public void testNbActions() throws Exception {
2.109 + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader();
2.110 + URL r = l.getResource("archetype-resources/nbactions.xml");
2.111 + assertNotNull(r, "Archetype nb file found");
2.112 +
2.113 + final XPathFactory fact = XPathFactory.newInstance();
2.114 + XPathExpression xp2 = fact.newXPath().compile(
2.115 + "//goal/text()"
2.116 + );
2.117 +
2.118 + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream());
2.119 + NodeList goals = (NodeList) xp2.evaluate(dom, XPathConstants.NODESET);
2.120 +
2.121 + for (int i = 0; i < goals.getLength(); i++) {
2.122 + String s = goals.item(i).getTextContent();
2.123 + if (s.contains("apidesign")) {
2.124 + assertFalse(s.matches(".*apidesign.*[0-9].*"), "No numbers: " + s);
2.125 + }
2.126 + }
2.127 + }
2.128 +
2.129 + static String findCurrentVersion() throws XPathExpressionException, IOException, ParserConfigurationException, SAXException, XPathFactoryConfigurationException {
2.130 + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader();
2.131 + URL u = l.getResource("META-INF/maven/org.apidesign.html/knockout4j-archetype/pom.xml");
2.132 + assertNotNull(u, "Own pom found: " + System.getProperty("java.class.path"));
2.133 +
2.134 + final XPathFactory fact = XPathFactory.newInstance();
2.135 + fact.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
2.136 +
2.137 + XPathExpression xp = fact.newXPath().compile("project/version/text()");
2.138 +
2.139 + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(u.openStream());
2.140 + return xp.evaluate(dom);
2.141 + }
2.142 +}
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/ko-archetype-test/src/test/java/org/apidesign/html/archetype/test/VerifyArchetypeTest.java Mon Jun 24 17:49:27 2013 +0200
3.3 @@ -0,0 +1,119 @@
3.4 +/**
3.5 + * HTML via Java(tm) Language Bindings
3.6 + * Copyright (C) 2013 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. apidesign.org
3.16 + * designates this particular file as subject to the
3.17 + * "Classpath" exception as provided by apidesign.org
3.18 + * in the License file that accompanied this code.
3.19 + *
3.20 + * You should have received a copy of the GNU General Public License
3.21 + * along with this program. Look for COPYING file in the top folder.
3.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
3.23 + */
3.24 +package org.apidesign.html.archetype.test;
3.25 +
3.26 +import java.io.File;
3.27 +import java.util.Properties;
3.28 +import java.util.zip.ZipFile;
3.29 +import org.apache.maven.it.Verifier;
3.30 +import org.testng.annotations.Test;
3.31 +import static org.testng.Assert.*;
3.32 +
3.33 +/**
3.34 + *
3.35 + * @author Jaroslav Tulach <jtulach@netbeans.org>
3.36 + */
3.37 +public class VerifyArchetypeTest {
3.38 + @Test public void fxBrwsrCompiles() throws Exception {
3.39 + final File dir = new File("target/tests/fxcompile/").getAbsoluteFile();
3.40 + generateFromArchetype(dir);
3.41 +
3.42 + File created = new File(dir, "o-a-test");
3.43 + assertTrue(created.isDirectory(), "Project created");
3.44 + assertTrue(new File(created, "pom.xml").isFile(), "Pom file is in there");
3.45 +
3.46 + Verifier v = new Verifier(created.getAbsolutePath());
3.47 + v.executeGoal("verify");
3.48 +
3.49 + v.verifyErrorFreeLog();
3.50 +
3.51 + for (String l : v.loadFile(v.getBasedir(), v.getLogFileName(), false)) {
3.52 + if (l.contains("j2js")) {
3.53 + fail("No pre-compilaton:\n" + l);
3.54 + }
3.55 + }
3.56 +
3.57 + v.verifyTextInLog("org.apidesign.bck2brwsr.launcher.FXBrwsrLauncher");
3.58 + v.verifyTextInLog("fxcompile/o-a-test/target/o-a-test-1.0-SNAPSHOT-fxbrwsr.zip");
3.59 + }
3.60 +
3.61 + @Test public void bck2BrwsrCompiles() throws Exception {
3.62 + final File dir = new File("target/tests/b2bcompile/").getAbsoluteFile();
3.63 + generateFromArchetype(dir);
3.64 +
3.65 + File created = new File(dir, "o-a-test");
3.66 + assertTrue(created.isDirectory(), "Project created");
3.67 + assertTrue(new File(created, "pom.xml").isFile(), "Pom file is in there");
3.68 +
3.69 + Verifier v = new Verifier(created.getAbsolutePath());
3.70 + Properties sysProp = v.getSystemProperties();
3.71 + if (Boolean.getBoolean("java.awt.headless")) {
3.72 + sysProp.put("java.awt.headless", "true");
3.73 + }
3.74 + v.addCliOption("-Pbck2brwsr");
3.75 + v.executeGoal("verify");
3.76 +
3.77 + v.verifyErrorFreeLog();
3.78 +
3.79 + // does pre-compilation to JavaScript
3.80 + v.verifyTextInLog("j2js");
3.81 + // uses Bck2BrwsrLauncher
3.82 + v.verifyTextInLog("BaseHTTPLauncher stopServerAndBrwsr");
3.83 + // building zip:
3.84 + v.verifyTextInLog("b2bcompile/o-a-test/target/o-a-test-1.0-SNAPSHOT-bck2brwsr.zip");
3.85 +
3.86 + for (String l : v.loadFile(v.getBasedir(), v.getLogFileName(), false)) {
3.87 + if (l.contains("fxbrwsr")) {
3.88 + fail("No fxbrwsr:\n" + l);
3.89 + }
3.90 + }
3.91 +
3.92 + File zip = new File(new File(created, "target"), "o-a-test-1.0-SNAPSHOT-bck2brwsr.zip");
3.93 + assertTrue(zip.isFile(), "Zip file with website was created");
3.94 +
3.95 + ZipFile zf = new ZipFile(zip);
3.96 + assertNotNull(zf.getEntry("public_html/index.html"), "index.html found");
3.97 + assertNotNull(zf.getEntry("public_html/twitterExample.css"), "css file found");
3.98 +
3.99 + }
3.100 +
3.101 + private Verifier generateFromArchetype(final File dir, String... params) throws Exception {
3.102 + Verifier v = new Verifier(dir.getAbsolutePath());
3.103 + v.setAutoclean(false);
3.104 + v.setLogFileName("generate.log");
3.105 + v.deleteDirectory("");
3.106 + dir.mkdirs();
3.107 + Properties sysProp = v.getSystemProperties();
3.108 + sysProp.put("groupId", "org.apidesign.test");
3.109 + sysProp.put("artifactId", "o-a-test");
3.110 + sysProp.put("package", "org.apidesign.test.oat");
3.111 + sysProp.put("archetypeGroupId", "org.apidesign.html");
3.112 + sysProp.put("archetypeArtifactId", "knockout4j-archetype");
3.113 + sysProp.put("archetypeVersion", ArchetypeVersionTest.findCurrentVersion());
3.114 +
3.115 + for (String p : params) {
3.116 + v.addCliOption(p);
3.117 + }
3.118 + v.executeGoal("archetype:generate");
3.119 + v.verifyErrorFreeLog();
3.120 + return v;
3.121 + }
3.122 +}
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/ko-archetype/pom.xml Mon Jun 24 17:49:27 2013 +0200
4.3 @@ -0,0 +1,58 @@
4.4 +<?xml version="1.0" encoding="UTF-8"?>
4.5 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.6 + <modelVersion>4.0.0</modelVersion>
4.7 + <parent>
4.8 + <artifactId>html</artifactId>
4.9 + <groupId>org.apidesign</groupId>
4.10 + <version>0.4-SNAPSHOT</version>
4.11 + </parent>
4.12 + <groupId>org.apidesign.html</groupId>
4.13 + <artifactId>knockout4j-archetype</artifactId>
4.14 + <version>0.4-SNAPSHOT</version>
4.15 + <packaging>jar</packaging>
4.16 + <name>Knockout 4 Java Maven Archetype</name>
4.17 + <description>
4.18 + HTML page with Knockout.js bindings driven by application model
4.19 + written in Java. Use your favorite language to code. Use
4.20 + HTML as a lightweight rendering toolkit. Deploy using JavaFX or
4.21 + bck2brwsr virtual machine.
4.22 + </description>
4.23 + <build>
4.24 + <resources>
4.25 + <resource>
4.26 + <directory>src/main/resources</directory>
4.27 + <filtering>true</filtering>
4.28 + <includes>
4.29 + <include>**/pom.xml</include>
4.30 + </includes>
4.31 + </resource>
4.32 + <resource>
4.33 + <directory>src/main/resources</directory>
4.34 + <filtering>false</filtering>
4.35 + <excludes>
4.36 + <exclude>**/pom.xml</exclude>
4.37 + </excludes>
4.38 + </resource>
4.39 + </resources>
4.40 + <plugins>
4.41 + <plugin>
4.42 + <groupId>org.apache.maven.plugins</groupId>
4.43 + <artifactId>maven-compiler-plugin</artifactId>
4.44 + <version>2.3.2</version>
4.45 + <configuration>
4.46 + <source>1.6</source>
4.47 + <target>1.6</target>
4.48 + </configuration>
4.49 + </plugin>
4.50 + <plugin>
4.51 + <groupId>org.apache.maven.plugins</groupId>
4.52 + <artifactId>maven-resources-plugin</artifactId>
4.53 + <version>2.6</version>
4.54 + <configuration>
4.55 + <escapeString>\</escapeString>
4.56 + <target>1.6</target>
4.57 + </configuration>
4.58 + </plugin>
4.59 + </plugins>
4.60 + </build>
4.61 +</project>
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/ko-archetype/src/main/java/org/apidesign/html/archetype/package-info.java Mon Jun 24 17:49:27 2013 +0200
5.3 @@ -0,0 +1,21 @@
5.4 +/**
5.5 + * HTML via Java(tm) Language Bindings
5.6 + * Copyright (C) 2013 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. apidesign.org
5.16 + * designates this particular file as subject to the
5.17 + * "Classpath" exception as provided by apidesign.org
5.18 + * in the License file that accompanied this code.
5.19 + *
5.20 + * You should have received a copy of the GNU General Public License
5.21 + * along with this program. Look for COPYING file in the top folder.
5.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
5.23 + */
5.24 +package org.apidesign.html.archetype;
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/ko-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml Mon Jun 24 17:49:27 2013 +0200
6.3 @@ -0,0 +1,44 @@
6.4 +<?xml version="1.0" encoding="UTF-8"?>
6.5 +<archetype-descriptor name="FX/Bck2Brwsr Example">
6.6 + <fileSets>
6.7 + <fileSet filtered="true" packaged="true">
6.8 + <directory>src/main/java</directory>
6.9 + <includes>
6.10 + <include>**/*.java</include>
6.11 + </includes>
6.12 + </fileSet>
6.13 + <fileSet filtered="true" packaged="true">
6.14 + <directory>src/main/resources</directory>
6.15 + <includes>
6.16 + <include>**/*.xhtml</include>
6.17 + <include>**/*.html</include>
6.18 + <include>**/*.css</include>
6.19 + </includes>
6.20 + </fileSet>
6.21 + <fileSet filtered="true" packaged="true">
6.22 + <directory>src/test/java</directory>
6.23 + <includes>
6.24 + <include>**/*Test.java</include>
6.25 + </includes>
6.26 + </fileSet>
6.27 + <fileSet filtered="true" packaged="false">
6.28 + <directory>src/main/assembly</directory>
6.29 + <includes>
6.30 + <include>**/*.xml</include>
6.31 + </includes>
6.32 + </fileSet>
6.33 + <fileSet filtered="false" packaged="false">
6.34 + <directory></directory>
6.35 + <includes>
6.36 + <include>nbactions*.xml</include>
6.37 + </includes>
6.38 + </fileSet>
6.39 + <fileSet filtered="true" packaged="false">
6.40 + <directory>assembly</directory>
6.41 + <includes>
6.42 + <include>fxbrwsr-assembly.xml</include>
6.43 + <include>bck2brwsr-assembly.xml</include>
6.44 + </includes>
6.45 + </fileSet>
6.46 + </fileSets>
6.47 +</archetype-descriptor>
6.48 \ No newline at end of file
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/ko-archetype/src/main/resources/archetype-resources/nbactions-bck2brwsr.xml Mon Jun 24 17:49:27 2013 +0200
7.3 @@ -0,0 +1,14 @@
7.4 +<?xml version="1.0" encoding="UTF-8"?>
7.5 +<actions>
7.6 + <action>
7.7 + <actionName>run</actionName>
7.8 + <goals>
7.9 + <goal>package</goal>
7.10 + <goal>bck2brwsr:brwsr</goal>
7.11 + </goals>
7.12 + <properties>
7.13 + <skipTests>true</skipTests>
7.14 + <bck2brwsr.obfuscationlevel>NONE</bck2brwsr.obfuscationlevel>
7.15 + </properties>
7.16 + </action>
7.17 +</actions>
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/ko-archetype/src/main/resources/archetype-resources/nbactions-fxbrwsr.xml Mon Jun 24 17:49:27 2013 +0200
8.3 @@ -0,0 +1,20 @@
8.4 +<?xml version="1.0" encoding="UTF-8"?>
8.5 +<actions>
8.6 + <action>
8.7 + <actionName>run</actionName>
8.8 + <goals>
8.9 + <goal>process-classes</goal>
8.10 + <goal>bck2brwsr:brwsr</goal>
8.11 + </goals>
8.12 + </action>
8.13 + <action>
8.14 + <actionName>debug</actionName>
8.15 + <goals>
8.16 + <goal>process-classes</goal>
8.17 + <goal>bck2brwsr:brwsr</goal>
8.18 + </goals>
8.19 + <properties>
8.20 + <jpda.listen>maven</jpda.listen>
8.21 + </properties>
8.22 + </action>
8.23 +</actions>
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/ko-archetype/src/main/resources/archetype-resources/nbactions.xml Mon Jun 24 17:49:27 2013 +0200
9.3 @@ -0,0 +1,20 @@
9.4 +<?xml version="1.0" encoding="UTF-8"?>
9.5 +<actions>
9.6 + <action>
9.7 + <actionName>run</actionName>
9.8 + <goals>
9.9 + <goal>process-classes</goal>
9.10 + <goal>bck2brwsr:brwsr</goal>
9.11 + </goals>
9.12 + </action>
9.13 + <action>
9.14 + <actionName>debug</actionName>
9.15 + <goals>
9.16 + <goal>process-classes</goal>
9.17 + <goal>bck2brwsr:brwsr</goal>
9.18 + </goals>
9.19 + <properties>
9.20 + <jpda.listen>maven</jpda.listen>
9.21 + </properties>
9.22 + </action>
9.23 +</actions>
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
10.2 +++ b/ko-archetype/src/main/resources/archetype-resources/pom.xml Mon Jun 24 17:49:27 2013 +0200
10.3 @@ -0,0 +1,266 @@
10.4 +<?xml version="1.0"?>
10.5 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10.6 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
10.7 + <modelVersion>4.0.0</modelVersion>
10.8 +
10.9 + <groupId>\${groupId}</groupId>
10.10 + <artifactId>\${artifactId}</artifactId>
10.11 + <version>\${version}</version>
10.12 + <packaging>jar</packaging>
10.13 +
10.14 + <name>\${artifactId}</name>
10.15 +
10.16 + <repositories>
10.17 + <repository>
10.18 + <id>java.net</id>
10.19 + <name>Java.net</name>
10.20 + <url>https://maven.java.net/content/repositories/releases/</url>
10.21 + <snapshots>
10.22 + <enabled>true</enabled>
10.23 + </snapshots>
10.24 + </repository>
10.25 + <repository>
10.26 + <id>netbeans</id>
10.27 + <name>NetBeans</name>
10.28 + <url>http://bits.netbeans.org/maven2/</url>
10.29 + </repository>
10.30 + </repositories>
10.31 + <pluginRepositories>
10.32 + <pluginRepository>
10.33 + <id>java.net</id>
10.34 + <name>Java.net</name>
10.35 + <url>https://maven.java.net/content/repositories/releases/</url>
10.36 + <snapshots>
10.37 + <enabled>true</enabled>
10.38 + </snapshots>
10.39 + </pluginRepository>
10.40 + </pluginRepositories>
10.41 +
10.42 + <properties>
10.43 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
10.44 + <net.java.html.version>${project.version}</net.java.html.version>
10.45 + <bck2brwsr.version>${bck2brwsr.version}</bck2brwsr.version>
10.46 + <bck2brwsr.launcher.version>${bck2brwsr.launcher.version}</bck2brwsr.launcher.version>
10.47 + <bck2brwsr.obfuscationlevel>MINIMAL</bck2brwsr.obfuscationlevel>
10.48 + <brwsr.startpage>\${package.replace('.','/')}/index.html</brwsr.startpage>
10.49 + </properties>
10.50 + <build>
10.51 + <plugins>
10.52 + <plugin>
10.53 + <groupId>org.apidesign.bck2brwsr</groupId>
10.54 + <artifactId>bck2brwsr-maven-plugin</artifactId>
10.55 + <version>\${bck2brwsr.launcher.version}</version>
10.56 + <executions>
10.57 + <execution>
10.58 + <goals>
10.59 + <goal>brwsr</goal>
10.60 + </goals>
10.61 + </execution>
10.62 + </executions>
10.63 + <configuration>
10.64 + <startpage>\${brwsr.startpage}</startpage>
10.65 + <launcher>\${brwsr}</launcher>
10.66 + </configuration>
10.67 + </plugin>
10.68 + <plugin>
10.69 + <groupId>org.apache.maven.plugins</groupId>
10.70 + <artifactId>maven-compiler-plugin</artifactId>
10.71 + <version>2.3.2</version>
10.72 + <configuration>
10.73 + <source>1.7</source>
10.74 + <target>1.7</target>
10.75 + </configuration>
10.76 + </plugin>
10.77 + <plugin>
10.78 + <groupId>org.apache.maven.plugins</groupId>
10.79 + <artifactId>maven-surefire-plugin</artifactId>
10.80 + <version>2.14.1</version>
10.81 + <configuration>
10.82 + <systemPropertyVariables>
10.83 + <vmtest.brwsrs>\${brwsr}</vmtest.brwsrs>
10.84 + </systemPropertyVariables>
10.85 + </configuration>
10.86 + </plugin>
10.87 + <plugin>
10.88 + <groupId>org.apache.maven.plugins</groupId>
10.89 + <artifactId>maven-jar-plugin</artifactId>
10.90 + <version>2.4</version>
10.91 + <configuration>
10.92 + <archive>
10.93 + <manifest>
10.94 + <addClasspath>true</addClasspath>
10.95 + <classpathPrefix>lib/</classpathPrefix>
10.96 + </manifest>
10.97 + </archive>
10.98 + </configuration>
10.99 + </plugin>
10.100 + <plugin>
10.101 + <groupId>org.apache.maven.plugins</groupId>
10.102 + <artifactId>maven-deploy-plugin</artifactId>
10.103 + <version>2.7</version>
10.104 + <configuration>
10.105 + <skip>true</skip>
10.106 + </configuration>
10.107 + </plugin>
10.108 + </plugins>
10.109 + </build>
10.110 +
10.111 + <dependencies>
10.112 + <dependency>
10.113 + <groupId>org.testng</groupId>
10.114 + <artifactId>testng</artifactId>
10.115 + <version>6.5.2</version>
10.116 + <scope>test</scope>
10.117 + </dependency>
10.118 + <dependency>
10.119 + <groupId>org.apidesign.bck2brwsr</groupId>
10.120 + <artifactId>vmtest</artifactId>
10.121 + <version>\${bck2brwsr.version}</version>
10.122 + <scope>test</scope>
10.123 + </dependency>
10.124 + <dependency>
10.125 + <groupId>org.apidesign.html</groupId>
10.126 + <artifactId>net.java.html.json</artifactId>
10.127 + <version>\${net.java.html.version}</version>
10.128 + <type>jar</type>
10.129 + </dependency>
10.130 + </dependencies>
10.131 + <profiles>
10.132 + <profile>
10.133 + <id>fxbrwsr</id>
10.134 + <activation>
10.135 + <activeByDefault>true</activeByDefault>
10.136 + </activation>
10.137 + <properties>
10.138 + <brwsr>fxbrwsr</brwsr>
10.139 + </properties>
10.140 + <build>
10.141 + <plugins>
10.142 + <plugin>
10.143 + <groupId>org.apache.maven.plugins</groupId>
10.144 + <artifactId>maven-jar-plugin</artifactId>
10.145 + <version>2.4</version>
10.146 + <configuration>
10.147 + <archive>
10.148 + <manifest>
10.149 + <mainClass>org.apidesign.bck2brwsr.launcher.FXBrwsrLauncher</mainClass>
10.150 + <addClasspath>true</addClasspath>
10.151 + <classpathPrefix>lib/</classpathPrefix>
10.152 + </manifest>
10.153 + <manifestEntries>
10.154 + <StartPage>\${brwsr.startpage}</StartPage>
10.155 + </manifestEntries>
10.156 + </archive>
10.157 + </configuration>
10.158 + </plugin>
10.159 + <plugin>
10.160 + <artifactId>maven-assembly-plugin</artifactId>
10.161 + <version>2.4</version>
10.162 + <executions>
10.163 + <execution>
10.164 + <id>distro-assembly</id>
10.165 + <phase>package</phase>
10.166 + <goals>
10.167 + <goal>single</goal>
10.168 + </goals>
10.169 + <configuration>
10.170 + <descriptors>
10.171 + <descriptor>src/main/assembly/fxbrwsr.xml</descriptor>
10.172 + </descriptors>
10.173 + </configuration>
10.174 + </execution>
10.175 + </executions>
10.176 + </plugin>
10.177 + </plugins>
10.178 + </build>
10.179 + <dependencies>
10.180 + <dependency>
10.181 + <groupId>org.apidesign.html</groupId>
10.182 + <artifactId>ko-fx</artifactId>
10.183 + <version>\${net.java.html.version}</version>
10.184 + </dependency>
10.185 + <dependency>
10.186 + <groupId>org.apidesign.bck2brwsr</groupId>
10.187 + <artifactId>launcher.fx</artifactId>
10.188 + <version>\${bck2brwsr.launcher.version}</version>
10.189 + <scope>runtime</scope>
10.190 + </dependency>
10.191 + </dependencies>
10.192 + </profile>
10.193 + <profile>
10.194 + <id>bck2brwsr</id>
10.195 + <activation>
10.196 + <property>
10.197 + <name>brwsr</name>
10.198 + <value>bck2brwsr</value>
10.199 + </property>
10.200 + </activation>
10.201 + <build>
10.202 + <plugins>
10.203 + <plugin>
10.204 + <groupId>org.apidesign.bck2brwsr</groupId>
10.205 + <artifactId>bck2brwsr-maven-plugin</artifactId>
10.206 + <executions>
10.207 + <execution>
10.208 + <goals>
10.209 + <goal>j2js</goal>
10.210 + </goals>
10.211 + </execution>
10.212 + </executions>
10.213 + <configuration>
10.214 + <javascript>\${project.build.directory}/bck2brwsr.js</javascript>
10.215 + <obfuscation>\${bck2brwsr.obfuscationlevel}</obfuscation>
10.216 + </configuration>
10.217 + </plugin>
10.218 + <plugin>
10.219 + <groupId>org.apache.maven.plugins</groupId>
10.220 + <artifactId>maven-compiler-plugin</artifactId>
10.221 + <configuration>
10.222 + <compilerArguments>
10.223 + <bootclasspath>netbeans.ignore.jdk.bootclasspath</bootclasspath>
10.224 + </compilerArguments>
10.225 + </configuration>
10.226 + </plugin>
10.227 + <plugin>
10.228 + <artifactId>maven-assembly-plugin</artifactId>
10.229 + <version>2.4</version>
10.230 + <executions>
10.231 + <execution>
10.232 + <id>distro-assembly</id>
10.233 + <phase>package</phase>
10.234 + <goals>
10.235 + <goal>single</goal>
10.236 + </goals>
10.237 + <configuration>
10.238 + <descriptors>
10.239 + <descriptor>src/main/assembly/bck2brwsr.xml</descriptor>
10.240 + </descriptors>
10.241 + </configuration>
10.242 + </execution>
10.243 + </executions>
10.244 + </plugin>
10.245 + </plugins>
10.246 + </build>
10.247 + <dependencies>
10.248 + <dependency>
10.249 + <groupId>org.apidesign.bck2brwsr</groupId>
10.250 + <artifactId>emul</artifactId>
10.251 + <version>\${bck2brwsr.version}</version>
10.252 + <classifier>rt</classifier>
10.253 + </dependency>
10.254 + <dependency>
10.255 + <groupId>org.apidesign.html</groupId>
10.256 + <artifactId>ko-bck2brwsr</artifactId>
10.257 + <version>\${net.java.html.version}</version>
10.258 + <scope>runtime</scope>
10.259 + </dependency>
10.260 + <dependency>
10.261 + <groupId>org.apidesign.bck2brwsr</groupId>
10.262 + <artifactId>launcher.http</artifactId>
10.263 + <version>\${bck2brwsr.launcher.version}</version>
10.264 + <scope>test</scope>
10.265 + </dependency>
10.266 + </dependencies>
10.267 + </profile>
10.268 + </profiles>
10.269 +</project>
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
11.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/main/assembly/bck2brwsr.xml Mon Jun 24 17:49:27 2013 +0200
11.3 @@ -0,0 +1,43 @@
11.4 +<?xml version="1.0"?>
11.5 +<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
11.6 + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
11.7 +
11.8 + <id>bck2brwsr</id>
11.9 + <formats>
11.10 + <format>zip</format>
11.11 + </formats>
11.12 + <baseDirectory>public_html</baseDirectory>
11.13 + <dependencySets>
11.14 + <dependencySet>
11.15 + <useProjectArtifact>false</useProjectArtifact>
11.16 + <scope>runtime</scope>
11.17 + <outputDirectory>lib</outputDirectory>
11.18 + <includes>
11.19 + <include>*:jar</include>
11.20 + <include>*:rt</include>
11.21 + </includes>
11.22 + </dependencySet>
11.23 + </dependencySets>
11.24 + <fileSets>
11.25 + <fileSet>
11.26 + <directory>${project.build.directory}/classes/${package.replace('.','/')}/</directory>
11.27 + <includes>
11.28 + <include>**/*</include>
11.29 + </includes>
11.30 + <excludes>
11.31 + <exclude>**/*.class</exclude>
11.32 + </excludes>
11.33 + <outputDirectory>/</outputDirectory>
11.34 + </fileSet>
11.35 + </fileSets>
11.36 + <files>
11.37 + <file>
11.38 + <source>${project.build.directory}/${project.build.finalName}.jar</source>
11.39 + <outputDirectory>/</outputDirectory>
11.40 + </file>
11.41 + <file>
11.42 + <source>${project.build.directory}/bck2brwsr.js</source>
11.43 + <outputDirectory>/</outputDirectory>
11.44 + </file>
11.45 + </files>
11.46 +</assembly>
11.47 \ No newline at end of file
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
12.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/main/assembly/fxbrwsr.xml Mon Jun 24 17:49:27 2013 +0200
12.3 @@ -0,0 +1,23 @@
12.4 +<?xml version="1.0"?>
12.5 +<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
12.6 + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
12.7 +
12.8 + <id>fxbrwsr</id>
12.9 + <formats>
12.10 + <format>zip</format>
12.11 + </formats>
12.12 + <baseDirectory>${project.build.finalName}-fxbrwsr</baseDirectory>
12.13 + <dependencySets>
12.14 + <dependencySet>
12.15 + <useProjectArtifact>false</useProjectArtifact>
12.16 + <scope>runtime</scope>
12.17 + <outputDirectory>lib</outputDirectory>
12.18 + </dependencySet>
12.19 + </dependencySets>
12.20 + <files>
12.21 + <file>
12.22 + <source>${project.build.directory}/${project.build.finalName}.jar</source>
12.23 + <outputDirectory>/</outputDirectory>
12.24 + </file>
12.25 + </files>
12.26 +</assembly>
12.27 \ No newline at end of file
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
13.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/main/java/TwitterClient.java Mon Jun 24 17:49:27 2013 +0200
13.3 @@ -0,0 +1,178 @@
13.4 +package ${package};
13.5 +
13.6 +import java.util.Arrays;
13.7 +import java.util.List;
13.8 +import net.java.html.json.ComputedProperty;
13.9 +import net.java.html.json.Function;
13.10 +import net.java.html.json.Model;
13.11 +import net.java.html.json.OnPropertyChange;
13.12 +import net.java.html.json.OnReceive;
13.13 +import net.java.html.json.Property;
13.14 +
13.15 +@Model(className="TwitterModel", properties={
13.16 + @Property(name="savedLists", type=Tweeters.class, array = true),
13.17 + @Property(name="activeTweetersName", type=String.class),
13.18 + @Property(name="activeTweeters", type=String.class, array = true),
13.19 + @Property(name="userNameToAdd", type=String.class),
13.20 + @Property(name="loading", type=boolean.class),
13.21 + @Property(name="currentTweets", type=Tweet.class, array = true)
13.22 +})
13.23 +public class TwitterClient {
13.24 + @Model(className = "Tweeters", properties = {
13.25 + @Property(name="name", type = String.class),
13.26 + @Property(name="userNames", type = String.class, array = true)
13.27 + })
13.28 + static class Twttrs {
13.29 + }
13.30 + @Model(className = "Tweet", properties = {
13.31 + @Property(name = "from_user", type = String.class),
13.32 + @Property(name = "from_user_id", type = int.class),
13.33 + @Property(name = "profile_image_url", type = String.class),
13.34 + @Property(name = "text", type = String.class),
13.35 + @Property(name = "created_at", type = String.class),
13.36 + })
13.37 + static final class Twt {
13.38 + @ComputedProperty static String html(String text) {
13.39 + StringBuilder sb = new StringBuilder(320);
13.40 + for (int pos = 0;;) {
13.41 + int http = text.indexOf("http", pos);
13.42 + if (http == -1) {
13.43 + sb.append(text.substring(pos));
13.44 + return sb.toString();
13.45 + }
13.46 + int spc = text.indexOf(' ', http);
13.47 + if (spc == -1) {
13.48 + spc = text.length();
13.49 + }
13.50 + sb.append(text.substring(pos, http));
13.51 + String url = text.substring(http, spc);
13.52 + sb.append("<a href='").append(url).append("'>").append(url).append("</a>");
13.53 + pos = spc;
13.54 + }
13.55 + }
13.56 +
13.57 + @ComputedProperty static String userUrl(String from_user) {
13.58 + return "http://twitter.com/" + from_user;
13.59 + }
13.60 + }
13.61 + @Model(className = "TwitterQuery", properties = {
13.62 + @Property(array = true, name = "results", type = Twt.class)
13.63 + })
13.64 + public static final class TwttrQr {
13.65 + }
13.66 +
13.67 + @OnReceive(url="{root}/search.json?{query}&callback={me}", jsonp="me")
13.68 + static void queryTweets(TwitterModel page, TwitterQuery q) {
13.69 + page.getCurrentTweets().clear();
13.70 + page.getCurrentTweets().addAll(q.getResults());
13.71 + page.setLoading(false);
13.72 + }
13.73 +
13.74 + @OnPropertyChange("activeTweetersName")
13.75 + static void changeTweetersList(TwitterModel model) {
13.76 + Tweeters people = findByName(model.getSavedLists(), model.getActiveTweetersName());
13.77 + model.getActiveTweeters().clear();
13.78 + model.getActiveTweeters().addAll(people.getUserNames());
13.79 + }
13.80 +
13.81 + @OnPropertyChange({ "activeTweeters", "activeTweetersCount" })
13.82 + static void refreshTweets(TwitterModel model) {
13.83 + StringBuilder sb = new StringBuilder();
13.84 + sb.append("rpp=25&q=");
13.85 + String sep = "";
13.86 + for (String p : model.getActiveTweeters()) {
13.87 + sb.append(sep);
13.88 + sb.append("from:");
13.89 + sb.append(p);
13.90 + sep = " OR ";
13.91 + }
13.92 + model.setLoading(true);
13.93 + model.queryTweets("http://search.twitter.com", sb.toString());
13.94 + }
13.95 +
13.96 + static {
13.97 + final TwitterModel model = new TwitterModel();
13.98 + final List<Tweeters> svdLst = model.getSavedLists();
13.99 + svdLst.add(newTweeters("API Design", "JaroslavTulach"));
13.100 + svdLst.add(newTweeters("Celebrities", "JohnCleese", "MCHammer", "StephenFry", "algore", "StevenSanderson"));
13.101 + svdLst.add(newTweeters("Microsoft people", "BillGates", "shanselman", "ScottGu"));
13.102 + svdLst.add(newTweeters("NetBeans", "GeertjanW","monacotoni", "NetBeans", "petrjiricka"));
13.103 + svdLst.add(newTweeters("Tech pundits", "Scobleizer", "LeoLaporte", "techcrunch", "BoingBoing", "timoreilly", "codinghorror"));
13.104 +
13.105 + model.setActiveTweetersName("NetBeans");
13.106 +
13.107 + model.applyBindings();
13.108 + }
13.109 +
13.110 + @ComputedProperty
13.111 + static boolean hasUnsavedChanges(List<String> activeTweeters, List<Tweeters> savedLists, String activeTweetersName) {
13.112 + Tweeters tw = findByName(savedLists, activeTweetersName);
13.113 + if (activeTweeters == null) {
13.114 + return false;
13.115 + }
13.116 + return !tw.getUserNames().equals(activeTweeters);
13.117 + }
13.118 +
13.119 + @ComputedProperty
13.120 + static int activeTweetersCount(List<String> activeTweeters) {
13.121 + return activeTweeters.size();
13.122 + }
13.123 +
13.124 + @ComputedProperty
13.125 + static boolean userNameToAddIsValid(
13.126 + String userNameToAdd, String activeTweetersName, List<Tweeters> savedLists, List<String> activeTweeters
13.127 + ) {
13.128 + return userNameToAdd != null &&
13.129 + userNameToAdd.matches("[a-zA-Z0-9_]{1,15}") &&
13.130 + !activeTweeters.contains(userNameToAdd);
13.131 + }
13.132 +
13.133 + @Function
13.134 + static void deleteList(TwitterModel model) {
13.135 + final List<Tweeters> sl = model.getSavedLists();
13.136 + sl.remove(findByName(sl, model.getActiveTweetersName()));
13.137 + if (sl.isEmpty()) {
13.138 + final Tweeters t = new Tweeters();
13.139 + t.setName("New");
13.140 + sl.add(t);
13.141 + }
13.142 + model.setActiveTweetersName(sl.get(0).getName());
13.143 + }
13.144 +
13.145 + @Function
13.146 + static void saveChanges(TwitterModel model) {
13.147 + Tweeters t = findByName(model.getSavedLists(), model.getActiveTweetersName());
13.148 + int indx = model.getSavedLists().indexOf(t);
13.149 + if (indx != -1) {
13.150 + t.setName(model.getActiveTweetersName());
13.151 + t.getUserNames().clear();
13.152 + t.getUserNames().addAll(model.getActiveTweeters());
13.153 + }
13.154 + }
13.155 +
13.156 + @Function
13.157 + static void addUser(TwitterModel model) {
13.158 + String n = model.getUserNameToAdd();
13.159 + model.getActiveTweeters().add(n);
13.160 + }
13.161 + @Function
13.162 + static void removeUser(String data, TwitterModel model) {
13.163 + model.getActiveTweeters().remove(data);
13.164 + }
13.165 +
13.166 + private static Tweeters findByName(List<Tweeters> list, String name) {
13.167 + for (Tweeters l : list) {
13.168 + if (l.getName() != null && l.getName().equals(name)) {
13.169 + return l;
13.170 + }
13.171 + }
13.172 + return list.isEmpty() ? new Tweeters() : list.get(0);
13.173 + }
13.174 +
13.175 + private static Tweeters newTweeters(String listName, String... userNames) {
13.176 + Tweeters t = new Tweeters();
13.177 + t.setName(listName);
13.178 + t.getUserNames().addAll(Arrays.asList(userNames));
13.179 + return t;
13.180 + }
13.181 +}
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
14.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/main/resources/index.html Mon Jun 24 17:49:27 2013 +0200
14.3 @@ -0,0 +1,90 @@
14.4 +<?xml version="1.0" encoding="UTF-8"?>
14.5 +
14.6 +<!--
14.7 + Copied from knockout.js Twitter example:
14.8 + http://knockoutjs.com/examples/twitter.html
14.9 +-->
14.10 +
14.11 +<!DOCTYPE html>
14.12 +<html xmlns="http://www.w3.org/1999/xhtml">
14.13 + <head>
14.14 + <title>Bck2Brwsr's Twitter</title>
14.15 + </head>
14.16 + <body>
14.17 + <link href='twitterExample.css' rel='Stylesheet' ></link>
14.18 +
14.19 + <style type='text/css'>
14.20 + .liveExample select { height: 1.7em; }
14.21 + .liveExample button { height: 2em; }
14.22 + </style>
14.23 +
14.24 +
14.25 + <h2>Bck2Brwsr's Twitter</h2>
14.26 +
14.27 + <p>
14.28 + This code is based on original
14.29 + <a href="http://knockoutjs.com/examples/twitter.html">knockout.js
14.30 + Twitter example</a> and
14.31 + uses almost unmodified HTML page. It just changes the model. The model
14.32 + is written in Java language with the help of
14.33 + <a href="http://bck2brwsr.apidesign.org/javadoc/net.java.html.json/">
14.34 + Knockout/Java binding library
14.35 + </a>. The Java source code has about 180 lines and seems more
14.36 + dense and shorter than the original JavaScript model.
14.37 + </p>
14.38 + <p>
14.39 + The project has two profiles. Either it executes in real Java virtual
14.40 + machine and renders using JavaFX's WebView (use <code>fxbrwsr</code> profile
14.41 + - the default). It can also run directly in a browser via
14.42 + <a href="http://bck2brwsr.apidesign.org">Bck2Brwsr</a> virtual machine
14.43 + (use <code>bck2brwsr</code> profile).
14.44 + </p>
14.45 +
14.46 + <div class='liveExample'>
14.47 + <div class='configuration'>
14.48 + <div class='listChooser'>
14.49 + <button data-bind='click: deleteList, enable: activeTweetersName'>Delete</button>
14.50 + <button data-bind='click: saveChanges, enable: hasUnsavedChanges'>Save</button>
14.51 + <select data-bind='options: savedLists, optionsValue: "name", value: activeTweetersName'> </select>
14.52 + </div>
14.53 +
14.54 + <p>Currently viewing <span data-bind='text: activeTweetersCount'> </span> user(s):</p>
14.55 + <div class='currentUsers' >
14.56 + <ul data-bind='foreach: activeTweeters'>
14.57 + <li>
14.58 + <button data-bind='click: $root.removeUser'>Remove</button>
14.59 + <div data-bind='text: $data'> </div>
14.60 + </li>
14.61 + </ul>
14.62 + </div>
14.63 +
14.64 + <form data-bind='submit: addUser'>
14.65 + <label>Add user:</label>
14.66 + <input data-bind='value: userNameToAdd, valueUpdate: "keyup", css: { invalid: !userNameToAddIsValid() }' />
14.67 + <button data-bind='enable: userNameToAddIsValid' type='submit'>Add</button>
14.68 + </form>
14.69 + </div>
14.70 + <div class='tweets'>
14.71 + <div class='loadingIndicator' data-bind="visible: loading">Loading...</div>
14.72 + <table data-bind='foreach: currentTweets' width='100%'>
14.73 + <tr>
14.74 + <td><img data-bind='attr: { src: profile_image_url }' /></td>
14.75 + <td>
14.76 + <a class='twitterUser' data-bind='attr: { href: userUrl }, text: from_user'> </a>
14.77 + <span data-bind='html: html'> </span>
14.78 + <div class='tweetInfo' data-bind='text: created_at'> </div>
14.79 + </td>
14.80 + </tr>
14.81 + </table>
14.82 + </div>
14.83 + </div>
14.84 +
14.85 + <script src="bck2brwsr.js"></script>
14.86 + <script type="text/javascript">
14.87 + var vm = bck2brwsr('${artifactId}-${version}.jar');
14.88 + vm.loadClass('${package}.TwitterClient');
14.89 + </script>
14.90 +
14.91 +
14.92 + </body>
14.93 +</html>
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
15.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/main/resources/twitterExample.css Mon Jun 24 17:49:27 2013 +0200
15.3 @@ -0,0 +1,32 @@
15.4 +/*
15.5 + Copied from knockout.js Twitter example:
15.6 + http://knockoutjs.com/examples/twitter.html
15.7 +*/
15.8 +
15.9 +.configuration, .tweets, .tweets td { font-family: Verdana; font-size: 13px; }
15.10 +.configuration { background-color: #DEDEDE; border: 2px solid gray; float:left; height: 40em; width: 40%; padding: 0.5em; border-right-width:0; }
15.11 +.tweets { width: 55%; border: 2px solid gray; height: 40em; overflow: scroll; overflow-x: hidden; background-color: Black; color: White; padding: 0.5em; position: relative; }
15.12 +.tweets table { border-width: 0;}
15.13 +.tweets tr { vertical-align: top; }
15.14 +.tweets td { padding: 0.4em 0.3em 1em 0.4em; border-width: 0; }
15.15 +.tweets img { width: 4em; }
15.16 +.tweetInfo { color: Gray; font-size: 0.9em; }
15.17 +.twitterUser { color: #77AAFF; text-decoration: none; font-size: 1.1em; font-weight: bold; }
15.18 +input.invalid { border: 1px solid red !important; background-color: #FFAAAA !important; }
15.19 +
15.20 +.listChooser select, .listChooser button { vertical-align:top; }
15.21 +.listChooser select { width: 60%; font-size:1.2em; height:1.4em; }
15.22 +.listChooser button { width: 19%; height:1.68em; float:right; }
15.23 +
15.24 +.currentUsers { height: 28em; overflow-y: auto; overflow-x: hidden; }
15.25 +.currentUsers button { float: right; height: 2.5em; margin: 0.1em; padding-left: 1em; padding-right: 1em; }
15.26 +.currentUsers ul, .configuration li { list-style: none; margin: 0; padding: 0 }
15.27 +.currentUsers li { height: 2.4em; font-size: 1.2em; background-color: #A7D0E3; border: 1px solid gray; margin-bottom: 0.3em; -webkit-border-radius: 5px; -moz-border-radius: 5px; -webkit-box-shadow: 0 0.2em 0.5em gray; -moz-box-shadow: 0 0.2em 0.5em gray; }
15.28 +.currentUsers li div { padding: 0.6em; }
15.29 +.currentUsers li:hover { background-color: #EEC; }
15.30 +
15.31 +.configuration form label { width: 25%; display: inline-block; text-align:right; overflow: hidden; }
15.32 +.configuration form input { width:40%; font-size: 1.3em; border:1px solid silver; background-color: White; padding: 0.1em; }
15.33 +.configuration form button { width: 20%; margin-left: 0.3em; height: 2em; }
15.34 +
15.35 +.loadingIndicator { position: absolute; top: 0.1em; left: 0.1em; font: 0.8em Arial; background-color: #229; color: White; padding: 0.2em 0.5em 0.2em 0.5em; }
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
16.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java Mon Jun 24 17:49:27 2013 +0200
16.3 @@ -0,0 +1,31 @@
16.4 +package ${package};
16.5 +
16.6 +import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
16.7 +import org.apidesign.bck2brwsr.vmtest.HtmlFragment;
16.8 +import org.apidesign.bck2brwsr.vmtest.VMTest;
16.9 +import org.testng.annotations.Factory;
16.10 +
16.11 +/** Sometimes it is useful to run tests inside of the real browser.
16.12 + * To do that just annotate your method with {@link org.apidesign.bck2brwsr.vmtest.BrwsrTest}
16.13 + * and that is it. If your code references elements on the HTML page,
16.14 + * you can pass in an {@link org.apidesign.bck2brwsr.vmtest.HtmlFragment} which
16.15 + * will be made available on the page before your test starts.
16.16 + */
16.17 +public class IntegrationTest {
16.18 +
16.19 + /** Write to testing code here. Use <code>assert</code> (but not TestNG's
16.20 + * Assert, as TestNG is not compiled with target 1.6 yet).
16.21 + */
16.22 + @HtmlFragment(
16.23 + "<h1>Put this snippet on the HTML page</h1>\n"
16.24 + )
16.25 + @BrwsrTest
16.26 + public void runThisTestInABrowser() {
16.27 + }
16.28 +
16.29 + @Factory
16.30 + public static Object[] create() {
16.31 + return VMTest.create(IntegrationTest.class);
16.32 + }
16.33 +
16.34 +}
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
17.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/test/java/TwitterClientTest.java Mon Jun 24 17:49:27 2013 +0200
17.3 @@ -0,0 +1,50 @@
17.4 +package ${package};
17.5 +
17.6 +import java.util.List;
17.7 +import net.java.html.BrwsrCtx;
17.8 +import net.java.html.json.Models;
17.9 +import static org.testng.Assert.*;
17.10 +import org.testng.annotations.BeforeMethod;
17.11 +import org.testng.annotations.Test;
17.12 +
17.13 +/** We can unit test the TwitterModel smoothly.
17.14 + */
17.15 +public class TwitterClientTest {
17.16 + private TwitterModel model;
17.17 +
17.18 +
17.19 + @BeforeMethod
17.20 + public void initModel() {
17.21 + model = Models.bind(new TwitterModel(), BrwsrCtx.EMPTY);
17.22 + }
17.23 +
17.24 + @Test public void testIsValidToAdd() {
17.25 + model.setUserNameToAdd("Joe");
17.26 + Tweeters t = Models.bind(new Tweeters(), BrwsrCtx.EMPTY);
17.27 + t.setName("test");
17.28 + model.getSavedLists().add(t);
17.29 + model.setActiveTweetersName("test");
17.30 +
17.31 + assertTrue(model.isUserNameToAddIsValid(), "Joe is OK");
17.32 + TwitterClient.addUser(model);
17.33 + assertFalse(model.isUserNameToAddIsValid(), "Can't add Joe for the 2nd time");
17.34 + assertEquals(t.getUserNames().size(), 0, "Original tweeters list remains empty");
17.35 +
17.36 + List<String> mod = model.getActiveTweeters();
17.37 + assertTrue(model.isHasUnsavedChanges(), "We have modifications");
17.38 + assertEquals(mod.size(), 1, "One element in the list");
17.39 + assertEquals(mod.get(0), "Joe", "Its name is Joe");
17.40 +
17.41 + assertSame(model.getActiveTweeters(), mod, "Editing list is the modified one");
17.42 +
17.43 + TwitterClient.saveChanges(model);
17.44 + assertFalse(model.isHasUnsavedChanges(), "Does not have anything to save");
17.45 +
17.46 + assertSame(model.getActiveTweeters(), mod, "Still editing the old modified one");
17.47 + }
17.48 +
17.49 + @Test public void httpAtTheEnd() {
17.50 + String res = TwitterClient.Twt.html("Ahoj http://kuk");
17.51 + assertEquals(res, "Ahoj <a href='http://kuk'>http://kuk</a>");
17.52 + }
17.53 +}
18.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
18.2 +++ b/ko-archetype/src/main/resources/archetype-resources/src/test/java/TwitterProtocolTest.java Mon Jun 24 17:49:27 2013 +0200
18.3 @@ -0,0 +1,73 @@
18.4 +package ${package};
18.5 +
18.6 +import org.apidesign.bck2brwsr.vmtest.BrwsrTest;
18.7 +import org.apidesign.bck2brwsr.vmtest.Http;
18.8 +import org.apidesign.bck2brwsr.vmtest.VMTest;
18.9 +import org.testng.annotations.Factory;
18.10 +
18.11 +public class TwitterProtocolTest {
18.12 + private TwitterModel page;
18.13 + @Http(@Http.Resource(
18.14 + path = "/search.json",
18.15 + mimeType = "application/json",
18.16 + parameters = {"callback"},
18.17 + content = "$0({\"completed_in\":0.04,\"max_id\":320055706885689344,\"max_id_str\""
18.18 + + ":\"320055706885689344\",\"page\":1,\"query\":\"from%3AJaroslavTulach\",\"refresh_url\":"
18.19 + + "\"?since_id=320055706885689344&q=from%3AJaroslavTulach\","
18.20 + + "\"results\":[{\"created_at\":\"Fri, 05 Apr 2013 06:10:01 +0000\","
18.21 + + "\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,\"from_user_id_str\":"
18.22 + + "\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,\"id\":320055706885689344,"
18.23 + + "\"id_str\":\"320055706885689344\",\"iso_language_code\":\"en\",\"metadata\":{\"result_type\":"
18.24 + + "\"recent\"},\"profile_image_url\":\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
18.25 + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
18.26 + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":"
18.27 + + "\"@tom_enebo Amzng! Not that I would like #ruby, but I am really glad you guys stabilized the plugin + "
18.28 + + "made it work in #netbeans 7.3! Gd wrk.\",\"to_user\":\"tom_enebo\",\"to_user_id\":14498747,"
18.29 + + "\"to_user_id_str\":\"14498747\",\"to_user_name\":\"tom_enebo\",\"in_reply_to_status_id\":319832359509839872,"
18.30 + + "\"in_reply_to_status_id_str\":\"319832359509839872\"},{\"created_at\":\"Thu, 04 Apr 2013 07:33:06 +0000\","
18.31 + + "\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,\"from_user_id_str\":"
18.32 + + "\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,\"id\":319714227088678913,"
18.33 + + "\"id_str\":\"319714227088678913\",\"iso_language_code\":\"en\",\"metadata\":{\"result_type\":"
18.34 + + "\"recent\"},\"profile_image_url\":\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
18.35 + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
18.36 + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":"
18.37 + + "\"RT @drkrab: At #erlangfactory @joerl: Frameworks grow in complexity until nobody can use them.\"},"
18.38 + + "{\"created_at\":\"Tue, 02 Apr 2013 07:44:34 +0000\",\"from_user\":\"JaroslavTulach\","
18.39 + + "\"from_user_id\":420944648,\"from_user_id_str\":\"420944648\",\"from_user_name\":\"Jaroslav Tulach\","
18.40 + + "\"geo\":null,\"id\":318992336145248256,\"id_str\":\"318992336145248256\",\"iso_language_code\":\"en\","
18.41 + + "\"metadata\":{\"result_type\":\"recent\"},\"profile_image_url\":"
18.42 + + "\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
18.43 + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
18.44 + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":"
18.45 + + "\"Twitter renamed to twttr http:\\/\\/t.co\\/tqaN4T1xlZ - good, I don't have to rename #bck2brwsr!\"},"
18.46 + + "{\"created_at\":\"Sun, 31 Mar 2013 03:52:04 +0000\",\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,"
18.47 + + "\"from_user_id_str\":\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,"
18.48 + + "\"id\":318209051223789568,\"id_str\":\"318209051223789568\",\"iso_language_code\":\"en\",\"metadata\":"
18.49 + + "{\"result_type\":\"recent\"},\"profile_image_url\":"
18.50 + + "\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
18.51 + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\","
18.52 + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":"
18.53 + + "\"Math proofs without words. Ingenious: http:\\/\\/t.co\\/sz7yVbfpGw\"}],\"results_per_page\":100,"
18.54 + + "\"since_id\":0,\"since_id_str\":\"0\"})"
18.55 + ))
18.56 + @BrwsrTest public void readFromTwttr() throws InterruptedException {
18.57 + if (page == null) {
18.58 + page = new TwitterModel();
18.59 + page.applyBindings();
18.60 + page.queryTweets("", "q=xyz");
18.61 + }
18.62 +
18.63 + if (page.getCurrentTweets().isEmpty()) {
18.64 + throw new InterruptedException();
18.65 + }
18.66 +
18.67 + assert 4 == page.getCurrentTweets().size() : "Four tweets: " + page.getCurrentTweets();
18.68 +
18.69 + String firstDate = page.getCurrentTweets().get(0).getCreated_at();
18.70 + assert "Fri, 05 Apr 2013 06:10:01 +0000".equals(firstDate) : "Date is OK: " + firstDate;
18.71 + }
18.72 +
18.73 + @Factory public static Object[] create() {
18.74 + return VMTest.create(TwitterProtocolTest.class);
18.75 + }
18.76 +}
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
19.2 +++ b/ko-bck2brwsr/pom.xml Mon Jun 24 17:49:27 2013 +0200
19.3 @@ -0,0 +1,96 @@
19.4 +<?xml version="1.0"?>
19.5 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
19.6 + <modelVersion>4.0.0</modelVersion>
19.7 + <parent>
19.8 + <groupId>org.apidesign</groupId>
19.9 + <artifactId>html</artifactId>
19.10 + <version>0.4-SNAPSHOT</version>
19.11 + </parent>
19.12 + <groupId>org.apidesign.html</groupId>
19.13 + <artifactId>ko-bck2brwsr</artifactId>
19.14 + <version>0.4-SNAPSHOT</version>
19.15 + <name>Knockout.b2b</name>
19.16 + <url>http://maven.apache.org</url>
19.17 + <build>
19.18 + <plugins>
19.19 + <plugin>
19.20 + <groupId>org.apache.maven.plugins</groupId>
19.21 + <artifactId>maven-compiler-plugin</artifactId>
19.22 + <version>2.3.2</version>
19.23 + <configuration>
19.24 + <source>1.7</source>
19.25 + <target>1.7</target>
19.26 + </configuration>
19.27 + </plugin>
19.28 + <plugin>
19.29 + <groupId>org.apache.maven.plugins</groupId>
19.30 + <artifactId>maven-javadoc-plugin</artifactId>
19.31 + <configuration>
19.32 + <skip>false</skip>
19.33 + </configuration>
19.34 + </plugin>
19.35 + </plugins>
19.36 + </build>
19.37 + <dependencies>
19.38 + <dependency>
19.39 + <groupId>org.testng</groupId>
19.40 + <artifactId>testng</artifactId>
19.41 + <scope>test</scope>
19.42 + <exclusions>
19.43 + <exclusion>
19.44 + <artifactId>junit</artifactId>
19.45 + <groupId>junit</groupId>
19.46 + </exclusion>
19.47 + </exclusions>
19.48 + </dependency>
19.49 + <dependency>
19.50 + <groupId>org.netbeans.api</groupId>
19.51 + <artifactId>org-openide-util-lookup</artifactId>
19.52 + <scope>provided</scope>
19.53 + </dependency>
19.54 + <dependency>
19.55 + <groupId>org.apidesign.bck2brwsr</groupId>
19.56 + <artifactId>emul</artifactId>
19.57 + <version>${bck2brwsr.version}</version>
19.58 + <classifier>rt</classifier>
19.59 + <type>jar</type>
19.60 + <scope>compile</scope>
19.61 + </dependency>
19.62 + <dependency>
19.63 + <groupId>org.apidesign.bck2brwsr</groupId>
19.64 + <artifactId>vm4brwsr</artifactId>
19.65 + <version>${bck2brwsr.version}</version>
19.66 + <type>jar</type>
19.67 + <scope>test</scope>
19.68 + </dependency>
19.69 + <dependency>
19.70 + <groupId>org.apidesign.bck2brwsr</groupId>
19.71 + <artifactId>vmtest</artifactId>
19.72 + <version>${bck2brwsr.version}</version>
19.73 + <scope>test</scope>
19.74 + </dependency>
19.75 + <dependency>
19.76 + <groupId>org.apidesign.bck2brwsr</groupId>
19.77 + <artifactId>launcher.http</artifactId>
19.78 + <version>${bck2brwsr.launcher.version}</version>
19.79 + <scope>test</scope>
19.80 + </dependency>
19.81 + <dependency>
19.82 + <groupId>org.apidesign.html</groupId>
19.83 + <artifactId>net.java.html.json</artifactId>
19.84 + <version>0.4-SNAPSHOT</version>
19.85 + </dependency>
19.86 + <dependency>
19.87 + <groupId>org.apidesign.html</groupId>
19.88 + <artifactId>net.java.html.json.tck</artifactId>
19.89 + <version>0.4-SNAPSHOT</version>
19.90 + <scope>test</scope>
19.91 + </dependency>
19.92 + <dependency>
19.93 + <groupId>org.apidesign.bck2brwsr</groupId>
19.94 + <artifactId>core</artifactId>
19.95 + <version>${bck2brwsr.version}</version>
19.96 + <type>jar</type>
19.97 + </dependency>
19.98 + </dependencies>
19.99 +</project>
20.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
20.2 +++ b/ko-bck2brwsr/src/main/java/org/apidesign/html/ko2brwsr/BrwsrCtxImpl.java Mon Jun 24 17:49:27 2013 +0200
20.3 @@ -0,0 +1,129 @@
20.4 +/**
20.5 + * HTML via Java(tm) Language Bindings
20.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
20.7 + *
20.8 + * This program is free software: you can redistribute it and/or modify
20.9 + * it under the terms of the GNU General Public License as published by
20.10 + * the Free Software Foundation, version 2 of the License.
20.11 + *
20.12 + * This program is distributed in the hope that it will be useful,
20.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
20.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20.15 + * GNU General Public License for more details. apidesign.org
20.16 + * designates this particular file as subject to the
20.17 + * "Classpath" exception as provided by apidesign.org
20.18 + * in the License file that accompanied this code.
20.19 + *
20.20 + * You should have received a copy of the GNU General Public License
20.21 + * along with this program. Look for COPYING file in the top folder.
20.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
20.23 + */
20.24 +package org.apidesign.html.ko2brwsr;
20.25 +
20.26 +import java.io.ByteArrayOutputStream;
20.27 +import java.io.IOException;
20.28 +import java.io.InputStream;
20.29 +import java.io.InputStreamReader;
20.30 +import net.java.html.BrwsrCtx;
20.31 +import org.apidesign.html.context.spi.Contexts;
20.32 +import org.apidesign.html.json.spi.FunctionBinding;
20.33 +import org.apidesign.html.json.spi.JSONCall;
20.34 +import org.apidesign.html.json.spi.PropertyBinding;
20.35 +import org.apidesign.html.json.spi.Technology;
20.36 +import org.apidesign.html.json.spi.Transfer;
20.37 +
20.38 +/**
20.39 + *
20.40 + * @author Jaroslav Tulach <jtulach@netbeans.org>
20.41 + */
20.42 +final class BrwsrCtxImpl implements Technology<Object>, Transfer {
20.43 + private BrwsrCtxImpl() {}
20.44 +
20.45 + public static final BrwsrCtxImpl DEFAULT = new BrwsrCtxImpl();
20.46 +
20.47 + @Override
20.48 + public void extract(Object obj, String[] props, Object[] values) {
20.49 + ConvertTypes.extractJSON(obj, props, values);
20.50 + }
20.51 +
20.52 + @Override
20.53 + public void loadJSON(final JSONCall call) {
20.54 + class R implements Runnable {
20.55 + Object[] arr = { null };
20.56 + @Override
20.57 + public void run() {
20.58 + call.notifySuccess(arr[0]);
20.59 + }
20.60 + }
20.61 + R r = new R();
20.62 + if (call.isJSONP()) {
20.63 + String me = ConvertTypes.createJSONP(r.arr, r);
20.64 + ConvertTypes.loadJSONP(call.composeURL(me), me);
20.65 + } else {
20.66 + String data = null;
20.67 + if (call.isDoOutput()) {
20.68 + try {
20.69 + ByteArrayOutputStream bos = new ByteArrayOutputStream();
20.70 + call.writeData(bos);
20.71 + data = new String(bos.toByteArray(), "UTF-8");
20.72 + } catch (IOException ex) {
20.73 + call.notifyError(ex);
20.74 + }
20.75 + }
20.76 + ConvertTypes.loadJSON(call.composeURL(null), r.arr, r, call.getMethod(), data);
20.77 + }
20.78 + }
20.79 +
20.80 + @Override
20.81 + public Object wrapModel(Object model) {
20.82 + return model;
20.83 + }
20.84 +
20.85 + @Override
20.86 + public void bind(PropertyBinding b, Object model, Object data) {
20.87 + Knockout.bind(data, b, b.getPropertyName(),
20.88 + "getValue__Ljava_lang_Object_2",
20.89 + b.isReadOnly() ? null : "setValue__VLjava_lang_Object_2",
20.90 + false, false
20.91 + );
20.92 + }
20.93 +
20.94 + @Override
20.95 + public void valueHasMutated(Object data, String propertyName) {
20.96 + Knockout.valueHasMutated(data, propertyName);
20.97 + }
20.98 +
20.99 + @Override
20.100 + public void expose(FunctionBinding fb, Object model, Object d) {
20.101 + Knockout.expose(d, fb, fb.getFunctionName(), "call__VLjava_lang_Object_2Ljava_lang_Object_2");
20.102 + }
20.103 +
20.104 + @Override
20.105 + public void applyBindings(Object data) {
20.106 + Knockout.applyBindings(data);
20.107 + }
20.108 +
20.109 + @Override
20.110 + public Object wrapArray(Object[] arr) {
20.111 + return arr;
20.112 + }
20.113 +
20.114 + @Override
20.115 + public <M> M toModel(Class<M> modelClass, Object data) {
20.116 + return modelClass.cast(data);
20.117 + }
20.118 +
20.119 + @Override
20.120 + public Object toJSON(InputStream is) throws IOException {
20.121 + StringBuilder sb = new StringBuilder();
20.122 + InputStreamReader r = new InputStreamReader(is);
20.123 + for (;;) {
20.124 + int ch = r.read();
20.125 + if (ch == -1) {
20.126 + break;
20.127 + }
20.128 + sb.append((char)ch);
20.129 + }
20.130 + return ConvertTypes.parse(sb.toString());
20.131 + }
20.132 +}
21.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
21.2 +++ b/ko-bck2brwsr/src/main/java/org/apidesign/html/ko2brwsr/BrwsrCtxPrvdr.java Mon Jun 24 17:49:27 2013 +0200
21.3 @@ -0,0 +1,54 @@
21.4 +/**
21.5 + * HTML via Java(tm) Language Bindings
21.6 + * Copyright (C) 2013 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. apidesign.org
21.16 + * designates this particular file as subject to the
21.17 + * "Classpath" exception as provided by apidesign.org
21.18 + * in the License file that accompanied this code.
21.19 + *
21.20 + * You should have received a copy of the GNU General Public License
21.21 + * along with this program. Look for COPYING file in the top folder.
21.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
21.23 + */
21.24 +package org.apidesign.html.ko2brwsr;
21.25 +
21.26 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
21.27 +import org.apidesign.html.context.spi.Contexts;
21.28 +import org.apidesign.html.json.spi.Technology;
21.29 +import org.apidesign.html.json.spi.Transfer;
21.30 +import org.openide.util.lookup.ServiceProvider;
21.31 +
21.32 +/** This is an implementation package - just
21.33 + * include its JAR on classpath and use official {@link Context} API
21.34 + * to access the functionality.
21.35 + * <p>
21.36 + * Provides binding between models and <a href="http://bck2brwsr.apidesign.org">
21.37 + * Bck2Brwsr</a> VM.
21.38 + * Registers {@link ContextProvider}, so {@link ServiceLoader} can find it.
21.39 + *
21.40 + * @author Jaroslav Tulach <jtulach@netbeans.org>
21.41 + */
21.42 +@ServiceProvider(service = Contexts.Provider.class)
21.43 +public final class BrwsrCtxPrvdr implements Contexts.Provider {
21.44 +
21.45 + @Override
21.46 + public void fillContext(Contexts.Builder context, Class<?> requestor) {
21.47 + if (bck2BrwsrVM()) {
21.48 + context.register(Technology.class, BrwsrCtxImpl.DEFAULT, 50).
21.49 + register(Transfer.class, BrwsrCtxImpl.DEFAULT, 50);
21.50 + }
21.51 + }
21.52 +
21.53 + @JavaScriptBody(args = { }, body = "return true;")
21.54 + private static boolean bck2BrwsrVM() {
21.55 + return false;
21.56 + }
21.57 +}
22.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
22.2 +++ b/ko-bck2brwsr/src/main/java/org/apidesign/html/ko2brwsr/ConvertTypes.java Mon Jun 24 17:49:27 2013 +0200
22.3 @@ -0,0 +1,155 @@
22.4 +/**
22.5 + * HTML via Java(tm) Language Bindings
22.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
22.7 + *
22.8 + * This program is free software: you can redistribute it and/or modify
22.9 + * it under the terms of the GNU General Public License as published by
22.10 + * the Free Software Foundation, version 2 of the License.
22.11 + *
22.12 + * This program is distributed in the hope that it will be useful,
22.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
22.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22.15 + * GNU General Public License for more details. apidesign.org
22.16 + * designates this particular file as subject to the
22.17 + * "Classpath" exception as provided by apidesign.org
22.18 + * in the License file that accompanied this code.
22.19 + *
22.20 + * You should have received a copy of the GNU General Public License
22.21 + * along with this program. Look for COPYING file in the top folder.
22.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
22.23 + */
22.24 +package org.apidesign.html.ko2brwsr;
22.25 +
22.26 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
22.27 +
22.28 +/**
22.29 + *
22.30 + * @author Jaroslav Tulach <jtulach@netbeans.org>
22.31 + */
22.32 +final class ConvertTypes {
22.33 + ConvertTypes() {
22.34 + }
22.35 +
22.36 + public static String toString(Object object, String property) {
22.37 + Object ret = getProperty(object, property);
22.38 + return ret == null ? null : ret.toString();
22.39 + }
22.40 +
22.41 + public static double toDouble(Object object, String property) {
22.42 + Object ret = getProperty(object, property);
22.43 + return ret instanceof Number ? ((Number)ret).doubleValue() : Double.NaN;
22.44 + }
22.45 +
22.46 + public static int toInt(Object object, String property) {
22.47 + Object ret = getProperty(object, property);
22.48 + return ret instanceof Number ? ((Number)ret).intValue() : Integer.MIN_VALUE;
22.49 + }
22.50 +
22.51 + public static <T> T toModel(Class<T> modelClass, Object object, String property) {
22.52 + Object ret = getProperty(object, property);
22.53 + if (ret == null || modelClass.isInstance(ret)) {
22.54 + return modelClass.cast(ret);
22.55 + }
22.56 + throw new IllegalStateException("Value " + ret + " is not of type " + modelClass);
22.57 + }
22.58 +
22.59 + public static String toJSON(Object value) {
22.60 + if (value == null) {
22.61 + return "null";
22.62 + }
22.63 + if (value instanceof Enum) {
22.64 + value = value.toString();
22.65 + }
22.66 + if (value instanceof String) {
22.67 + return '"' +
22.68 + ((String)value).
22.69 + replace("\"", "\\\"").
22.70 + replace("\n", "\\n").
22.71 + replace("\r", "\\r").
22.72 + replace("\t", "\\t")
22.73 + + '"';
22.74 + }
22.75 + return value.toString();
22.76 + }
22.77 +
22.78 + @JavaScriptBody(args = { "object", "property" },
22.79 + body = "if (property === null) return object;\n"
22.80 + + "var p = object[property]; return p ? p : null;"
22.81 + )
22.82 + private static Object getProperty(Object object, String property) {
22.83 + return null;
22.84 + }
22.85 +
22.86 + public static String createJSONP(Object[] jsonResult, Runnable whenDone) {
22.87 + int h = whenDone.hashCode();
22.88 + String name;
22.89 + for (;;) {
22.90 + name = "jsonp" + Integer.toHexString(h);
22.91 + if (defineIfUnused(name, jsonResult, whenDone)) {
22.92 + return name;
22.93 + }
22.94 + h++;
22.95 + }
22.96 + }
22.97 +
22.98 + @JavaScriptBody(args = { "name", "arr", "run" }, body =
22.99 + "if (window[name]) return false;\n "
22.100 + + "window[name] = function(data) {\n "
22.101 + + " delete window[name];\n"
22.102 + + " var el = window.document.getElementById(name);\n"
22.103 + + " el.parentNode.removeChild(el);\n"
22.104 + + " arr[0] = data;\n"
22.105 + + " run.run__V();\n"
22.106 + + "};\n"
22.107 + + "return true;\n"
22.108 + )
22.109 + private static boolean defineIfUnused(String name, Object[] arr, Runnable run) {
22.110 + return true;
22.111 + }
22.112 +
22.113 + @JavaScriptBody(args = { "s" }, body = "return eval('(' + s + ')');")
22.114 + static Object parse(String s) {
22.115 + return s;
22.116 + }
22.117 +
22.118 + @JavaScriptBody(args = { "url", "arr", "callback", "method", "data" }, body = ""
22.119 + + "var request = new XMLHttpRequest();\n"
22.120 + + "if (!method) method = 'GET';\n"
22.121 + + "request.open(method, url, true);\n"
22.122 + + "request.setRequestHeader('Content-Type', 'application/json; charset=utf-8');\n"
22.123 + + "request.onreadystatechange = function() {\n"
22.124 + + " if (this.readyState!==4) return;\n"
22.125 + + " try {\n"
22.126 + + " arr[0] = eval('(' + this.response + ')');\n"
22.127 + + " } catch (error) {;\n"
22.128 + + " arr[0] = this.response;\n"
22.129 + + " }\n"
22.130 + + " callback.run__V();\n"
22.131 + + "};"
22.132 + + "if (data) request.send(data);"
22.133 + + "else request.send();"
22.134 + )
22.135 + static void loadJSON(
22.136 + String url, Object[] jsonResult, Runnable whenDone, String method, String data
22.137 + ) {
22.138 + }
22.139 +
22.140 + @JavaScriptBody(args = { "url", "jsonp" }, body =
22.141 + "var scrpt = window.document.createElement('script');\n "
22.142 + + "scrpt.setAttribute('src', url);\n "
22.143 + + "scrpt.setAttribute('id', jsonp);\n "
22.144 + + "scrpt.setAttribute('type', 'text/javascript');\n "
22.145 + + "var body = document.getElementsByTagName('body')[0];\n "
22.146 + + "body.appendChild(scrpt);\n"
22.147 + )
22.148 + static void loadJSONP(String url, String jsonp) {
22.149 +
22.150 + }
22.151 +
22.152 + public static void extractJSON(Object jsonObject, String[] props, Object[] values) {
22.153 + for (int i = 0; i < props.length; i++) {
22.154 + values[i] = getProperty(jsonObject, props[i]);
22.155 + }
22.156 + }
22.157 +
22.158 +}
23.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
23.2 +++ b/ko-bck2brwsr/src/main/java/org/apidesign/html/ko2brwsr/Knockout.java Mon Jun 24 17:49:27 2013 +0200
23.3 @@ -0,0 +1,134 @@
23.4 +/**
23.5 + * HTML via Java(tm) Language Bindings
23.6 + * Copyright (C) 2013 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. apidesign.org
23.16 + * designates this particular file as subject to the
23.17 + * "Classpath" exception as provided by apidesign.org
23.18 + * in the License file that accompanied this code.
23.19 + *
23.20 + * You should have received a copy of the GNU General Public License
23.21 + * along with this program. Look for COPYING file in the top folder.
23.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
23.23 + */
23.24 +package org.apidesign.html.ko2brwsr;
23.25 +
23.26 +import java.lang.reflect.Method;
23.27 +import java.util.List;
23.28 +import org.apidesign.bck2brwsr.core.ExtraJavaScript;
23.29 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
23.30 +
23.31 +/** Provides binding between models and bck2brwsr VM.
23.32 + *
23.33 + * @author Jaroslav Tulach <jtulach@netbeans.org>
23.34 + */
23.35 +@ExtraJavaScript(resource = "/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js")
23.36 +final class Knockout {
23.37 + /** used by tests */
23.38 + static Knockout next;
23.39 + private final Object model;
23.40 +
23.41 + Knockout(Object model) {
23.42 + this.model = model == null ? this : model;
23.43 + }
23.44 +
23.45 + public static <M> Knockout applyBindings(
23.46 + Object model, String[] propsGettersAndSetters,
23.47 + String[] methodsAndSignatures
23.48 + ) {
23.49 + applyImpl(propsGettersAndSetters, model.getClass(), model, model, methodsAndSignatures);
23.50 + return new Knockout(model);
23.51 + }
23.52 + public static <M> Knockout applyBindings(
23.53 + Class<M> modelClass, M model, String[] propsGettersAndSetters,
23.54 + String[] methodsAndSignatures
23.55 + ) {
23.56 + Knockout bindings = next;
23.57 + next = null;
23.58 + if (bindings == null) {
23.59 + bindings = new Knockout(null);
23.60 + }
23.61 + applyImpl(propsGettersAndSetters, modelClass, bindings, model, methodsAndSignatures);
23.62 + applyBindings(bindings);
23.63 + return bindings;
23.64 + }
23.65 +
23.66 + public void valueHasMutated(String prop) {
23.67 + valueHasMutated(model, prop);
23.68 + }
23.69 + @JavaScriptBody(args = { "self", "prop" }, body =
23.70 + "var p = self[prop]; if (p) p.valueHasMutated();"
23.71 + )
23.72 + public static void valueHasMutated(Object self, String prop) {
23.73 + }
23.74 +
23.75 +
23.76 + @JavaScriptBody(args = { "id", "ev" }, body = "ko.utils.triggerEvent(window.document.getElementById(id), ev.substring(2));")
23.77 + public static void triggerEvent(String id, String ev) {
23.78 + }
23.79 +
23.80 + @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive", "array" }, body =
23.81 + "var bnd = {\n"
23.82 + + " 'read': function() {\n"
23.83 + + " var v = model[getter]();\n"
23.84 + + " if (array) v = v.koArray(); else if (v !== null) v = v.valueOf();\n"
23.85 + + " return v;\n"
23.86 + + " },\n"
23.87 + + " 'owner': bindings\n"
23.88 + + "};\n"
23.89 + + "if (setter != null) {\n"
23.90 + + " bnd['write'] = function(val) {\n"
23.91 + + " var v = val === null ? null : val.valueOf();"
23.92 + + " model[setter](v);\n"
23.93 + + " };\n"
23.94 + + "}\n"
23.95 + + "bindings[prop] = ko['computed'](bnd);"
23.96 + )
23.97 + static void bind(
23.98 + Object bindings, Object model, String prop, String getter, String setter, boolean primitive, boolean array
23.99 + ) {
23.100 + }
23.101 +
23.102 + @JavaScriptBody(args = { "bindings", "model", "prop", "sig" }, body =
23.103 + "bindings[prop] = function(data, ev) { model[sig](data, ev); };"
23.104 + )
23.105 + static void expose(
23.106 + Object bindings, Object model, String prop, String sig
23.107 + ) {
23.108 + }
23.109 +
23.110 + @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);")
23.111 + static void applyBindings(Object bindings) {}
23.112 +
23.113 + private static void applyImpl(
23.114 + String[] propsGettersAndSetters,
23.115 + Class<?> modelClass,
23.116 + Object bindings,
23.117 + Object model,
23.118 + String[] methodsAndSignatures
23.119 + ) throws IllegalStateException, SecurityException {
23.120 + for (int i = 0; i < propsGettersAndSetters.length; i += 4) {
23.121 + try {
23.122 + Method getter = modelClass.getMethod(propsGettersAndSetters[i + 3]);
23.123 + bind(bindings, model, propsGettersAndSetters[i],
23.124 + propsGettersAndSetters[i + 1],
23.125 + propsGettersAndSetters[i + 2],
23.126 + getter.getReturnType().isPrimitive(),
23.127 + List.class.isAssignableFrom(getter.getReturnType()));
23.128 + } catch (NoSuchMethodException ex) {
23.129 + throw new IllegalStateException(ex.getMessage());
23.130 + }
23.131 + }
23.132 + for (int i = 0; i < methodsAndSignatures.length; i += 2) {
23.133 + expose(
23.134 + bindings, model, methodsAndSignatures[i], methodsAndSignatures[i + 1]);
23.135 + }
23.136 + }
23.137 +}
24.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
24.2 +++ b/ko-bck2brwsr/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js Mon Jun 24 17:49:27 2013 +0200
24.3 @@ -0,0 +1,3614 @@
24.4 +/*
24.5 + * HTML via Java(tm) Language Bindings
24.6 + * Copyright (C) 2013 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. apidesign.org
24.16 + * designates this particular file as subject to the
24.17 + * "Classpath" exception as provided by apidesign.org
24.18 + * in the License file that accompanied this code.
24.19 + *
24.20 + * You should have received a copy of the GNU General Public License
24.21 + * along with this program. Look for COPYING file in the top folder.
24.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
24.23 + */
24.24 +// Knockout JavaScript library v2.2.1
24.25 +// (c) Steven Sanderson - http://knockoutjs.com/
24.26 +// License: MIT (http://www.opensource.org/licenses/mit-license.php)
24.27 +
24.28 +(function(){
24.29 +var DEBUG=true;
24.30 +(function(window,document,navigator,jQuery,undefined){
24.31 +!function(factory) {
24.32 + // Support three module loading scenarios
24.33 + if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
24.34 + // [1] CommonJS/Node.js
24.35 + var target = module['exports'] || exports; // module.exports is for Node.js
24.36 + factory(target);
24.37 + } else if (typeof define === 'function' && define['amd']) {
24.38 + // [2] AMD anonymous module
24.39 + define(['exports'], factory);
24.40 + } else {
24.41 + // [3] No module loader (plain <script> tag) - put directly in global namespace
24.42 + factory(window['ko'] = {});
24.43 + }
24.44 +}(function(koExports){
24.45 +// Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
24.46 +// In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
24.47 +var ko = typeof koExports !== 'undefined' ? koExports : {};
24.48 +// Google Closure Compiler helpers (used only to make the minified file smaller)
24.49 +ko.exportSymbol = function(koPath, object) {
24.50 + var tokens = koPath.split(".");
24.51 +
24.52 + // In the future, "ko" may become distinct from "koExports" (so that non-exported objects are not reachable)
24.53 + // At that point, "target" would be set to: (typeof koExports !== "undefined" ? koExports : ko)
24.54 + var target = ko;
24.55 +
24.56 + for (var i = 0; i < tokens.length - 1; i++)
24.57 + target = target[tokens[i]];
24.58 + target[tokens[tokens.length - 1]] = object;
24.59 +};
24.60 +ko.exportProperty = function(owner, publicName, object) {
24.61 + owner[publicName] = object;
24.62 +};
24.63 +ko.version = "2.2.1";
24.64 +
24.65 +ko.exportSymbol('version', ko.version);
24.66 +ko.utils = new (function () {
24.67 + var stringTrimRegex = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
24.68 +
24.69 + // 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)
24.70 + var knownEvents = {}, knownEventTypesByEventName = {};
24.71 + var keyEventTypeName = /Firefox\/2/i.test(navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents';
24.72 + knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress'];
24.73 + knownEvents['MouseEvents'] = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave'];
24.74 + for (var eventType in knownEvents) {
24.75 + var knownEventsForType = knownEvents[eventType];
24.76 + if (knownEventsForType.length) {
24.77 + for (var i = 0, j = knownEventsForType.length; i < j; i++)
24.78 + knownEventTypesByEventName[knownEventsForType[i]] = eventType;
24.79 + }
24.80 + }
24.81 + var eventsThatMustBeRegisteredUsingAttachEvent = { 'propertychange': true }; // Workaround for an IE9 issue - https://github.com/SteveSanderson/knockout/issues/406
24.82 +
24.83 + // Detect IE versions for bug workarounds (uses IE conditionals, not UA string, for robustness)
24.84 + // Note that, since IE 10 does not support conditional comments, the following logic only detects IE < 10.
24.85 + // Currently this is by design, since IE 10+ behaves correctly when treated as a standard browser.
24.86 + // If there is a future need to detect specific versions of IE10+, we will amend this.
24.87 + var ieVersion = (function() {
24.88 + var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i');
24.89 +
24.90 + // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
24.91 + while (
24.92 + div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->',
24.93 + iElems[0]
24.94 + );
24.95 + return version > 4 ? version : undefined;
24.96 + }());
24.97 + var isIe6 = ieVersion === 6,
24.98 + isIe7 = ieVersion === 7;
24.99 +
24.100 + function isClickOnCheckableElement(element, eventType) {
24.101 + if ((ko.utils.tagNameLower(element) !== "input") || !element.type) return false;
24.102 + if (eventType.toLowerCase() != "click") return false;
24.103 + var inputType = element.type;
24.104 + return (inputType == "checkbox") || (inputType == "radio");
24.105 + }
24.106 +
24.107 + return {
24.108 + fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],
24.109 +
24.110 + arrayForEach: function (array, action) {
24.111 + for (var i = 0, j = array.length; i < j; i++)
24.112 + action(array[i]);
24.113 + },
24.114 +
24.115 + arrayIndexOf: function (array, item) {
24.116 + if (typeof Array.prototype.indexOf == "function")
24.117 + return Array.prototype.indexOf.call(array, item);
24.118 + for (var i = 0, j = array.length; i < j; i++)
24.119 + if (array[i] === item)
24.120 + return i;
24.121 + return -1;
24.122 + },
24.123 +
24.124 + arrayFirst: function (array, predicate, predicateOwner) {
24.125 + for (var i = 0, j = array.length; i < j; i++)
24.126 + if (predicate.call(predicateOwner, array[i]))
24.127 + return array[i];
24.128 + return null;
24.129 + },
24.130 +
24.131 + arrayRemoveItem: function (array, itemToRemove) {
24.132 + var index = ko.utils.arrayIndexOf(array, itemToRemove);
24.133 + if (index >= 0)
24.134 + array.splice(index, 1);
24.135 + },
24.136 +
24.137 + arrayGetDistinctValues: function (array) {
24.138 + array = array || [];
24.139 + var result = [];
24.140 + for (var i = 0, j = array.length; i < j; i++) {
24.141 + if (ko.utils.arrayIndexOf(result, array[i]) < 0)
24.142 + result.push(array[i]);
24.143 + }
24.144 + return result;
24.145 + },
24.146 +
24.147 + arrayMap: function (array, mapping) {
24.148 + array = array || [];
24.149 + var result = [];
24.150 + for (var i = 0, j = array.length; i < j; i++)
24.151 + result.push(mapping(array[i]));
24.152 + return result;
24.153 + },
24.154 +
24.155 + arrayFilter: function (array, predicate) {
24.156 + array = array || [];
24.157 + var result = [];
24.158 + for (var i = 0, j = array.length; i < j; i++)
24.159 + if (predicate(array[i]))
24.160 + result.push(array[i]);
24.161 + return result;
24.162 + },
24.163 +
24.164 + arrayPushAll: function (array, valuesToPush) {
24.165 + if (valuesToPush instanceof Array)
24.166 + array.push.apply(array, valuesToPush);
24.167 + else
24.168 + for (var i = 0, j = valuesToPush.length; i < j; i++)
24.169 + array.push(valuesToPush[i]);
24.170 + return array;
24.171 + },
24.172 +
24.173 + extend: function (target, source) {
24.174 + if (source) {
24.175 + for(var prop in source) {
24.176 + if(source.hasOwnProperty(prop)) {
24.177 + target[prop] = source[prop];
24.178 + }
24.179 + }
24.180 + }
24.181 + return target;
24.182 + },
24.183 +
24.184 + emptyDomNode: function (domNode) {
24.185 + while (domNode.firstChild) {
24.186 + ko.removeNode(domNode.firstChild);
24.187 + }
24.188 + },
24.189 +
24.190 + moveCleanedNodesToContainerElement: function(nodes) {
24.191 + // Ensure it's a real array, as we're about to reparent the nodes and
24.192 + // we don't want the underlying collection to change while we're doing that.
24.193 + var nodesArray = ko.utils.makeArray(nodes);
24.194 +
24.195 + var container = document.createElement('div');
24.196 + for (var i = 0, j = nodesArray.length; i < j; i++) {
24.197 + container.appendChild(ko.cleanNode(nodesArray[i]));
24.198 + }
24.199 + return container;
24.200 + },
24.201 +
24.202 + cloneNodes: function (nodesArray, shouldCleanNodes) {
24.203 + for (var i = 0, j = nodesArray.length, newNodesArray = []; i < j; i++) {
24.204 + var clonedNode = nodesArray[i].cloneNode(true);
24.205 + newNodesArray.push(shouldCleanNodes ? ko.cleanNode(clonedNode) : clonedNode);
24.206 + }
24.207 + return newNodesArray;
24.208 + },
24.209 +
24.210 + setDomNodeChildren: function (domNode, childNodes) {
24.211 + ko.utils.emptyDomNode(domNode);
24.212 + if (childNodes) {
24.213 + for (var i = 0, j = childNodes.length; i < j; i++)
24.214 + domNode.appendChild(childNodes[i]);
24.215 + }
24.216 + },
24.217 +
24.218 + replaceDomNodes: function (nodeToReplaceOrNodeArray, newNodesArray) {
24.219 + var nodesToReplaceArray = nodeToReplaceOrNodeArray.nodeType ? [nodeToReplaceOrNodeArray] : nodeToReplaceOrNodeArray;
24.220 + if (nodesToReplaceArray.length > 0) {
24.221 + var insertionPoint = nodesToReplaceArray[0];
24.222 + var parent = insertionPoint.parentNode;
24.223 + for (var i = 0, j = newNodesArray.length; i < j; i++)
24.224 + parent.insertBefore(newNodesArray[i], insertionPoint);
24.225 + for (var i = 0, j = nodesToReplaceArray.length; i < j; i++) {
24.226 + ko.removeNode(nodesToReplaceArray[i]);
24.227 + }
24.228 + }
24.229 + },
24.230 +
24.231 + setOptionNodeSelectionState: function (optionNode, isSelected) {
24.232 + // IE6 sometimes throws "unknown error" if you try to write to .selected directly, whereas Firefox struggles with setAttribute. Pick one based on browser.
24.233 + if (ieVersion < 7)
24.234 + optionNode.setAttribute("selected", isSelected);
24.235 + else
24.236 + optionNode.selected = isSelected;
24.237 + },
24.238 +
24.239 + stringTrim: function (string) {
24.240 + return (string || "").replace(stringTrimRegex, "");
24.241 + },
24.242 +
24.243 + stringTokenize: function (string, delimiter) {
24.244 + var result = [];
24.245 + var tokens = (string || "").split(delimiter);
24.246 + for (var i = 0, j = tokens.length; i < j; i++) {
24.247 + var trimmed = ko.utils.stringTrim(tokens[i]);
24.248 + if (trimmed !== "")
24.249 + result.push(trimmed);
24.250 + }
24.251 + return result;
24.252 + },
24.253 +
24.254 + stringStartsWith: function (string, startsWith) {
24.255 + string = string || "";
24.256 + if (startsWith.length > string.length)
24.257 + return false;
24.258 + return string.substring(0, startsWith.length) === startsWith;
24.259 + },
24.260 +
24.261 + domNodeIsContainedBy: function (node, containedByNode) {
24.262 + if (containedByNode.compareDocumentPosition)
24.263 + return (containedByNode.compareDocumentPosition(node) & 16) == 16;
24.264 + while (node != null) {
24.265 + if (node == containedByNode)
24.266 + return true;
24.267 + node = node.parentNode;
24.268 + }
24.269 + return false;
24.270 + },
24.271 +
24.272 + domNodeIsAttachedToDocument: function (node) {
24.273 + return ko.utils.domNodeIsContainedBy(node, node.ownerDocument);
24.274 + },
24.275 +
24.276 + tagNameLower: function(element) {
24.277 + // For HTML elements, tagName will always be upper case; for XHTML elements, it'll be lower case.
24.278 + // Possible future optimization: If we know it's an element from an XHTML document (not HTML),
24.279 + // we don't need to do the .toLowerCase() as it will always be lower case anyway.
24.280 + return element && element.tagName && element.tagName.toLowerCase();
24.281 + },
24.282 +
24.283 + registerEventHandler: function (element, eventType, handler) {
24.284 + var mustUseAttachEvent = ieVersion && eventsThatMustBeRegisteredUsingAttachEvent[eventType];
24.285 + if (!mustUseAttachEvent && typeof jQuery != "undefined") {
24.286 + if (isClickOnCheckableElement(element, eventType)) {
24.287 + // For click events on checkboxes, jQuery interferes with the event handling in an awkward way:
24.288 + // it toggles the element checked state *after* the click event handlers run, whereas native
24.289 + // click events toggle the checked state *before* the event handler.
24.290 + // Fix this by intecepting the handler and applying the correct checkedness before it runs.
24.291 + var originalHandler = handler;
24.292 + handler = function(event, eventData) {
24.293 + var jQuerySuppliedCheckedState = this.checked;
24.294 + if (eventData)
24.295 + this.checked = eventData.checkedStateBeforeEvent !== true;
24.296 + originalHandler.call(this, event);
24.297 + this.checked = jQuerySuppliedCheckedState; // Restore the state jQuery applied
24.298 + };
24.299 + }
24.300 + jQuery(element)['bind'](eventType, handler);
24.301 + } else if (!mustUseAttachEvent && typeof element.addEventListener == "function")
24.302 + element.addEventListener(eventType, handler, false);
24.303 + else if (typeof element.attachEvent != "undefined")
24.304 + element.attachEvent("on" + eventType, function (event) {
24.305 + handler.call(element, event);
24.306 + });
24.307 + else
24.308 + throw new Error("Browser doesn't support addEventListener or attachEvent");
24.309 + },
24.310 +
24.311 + triggerEvent: function (element, eventType) {
24.312 + if (!(element && element.nodeType))
24.313 + throw new Error("element must be a DOM node when calling triggerEvent");
24.314 +
24.315 + if (typeof jQuery != "undefined") {
24.316 + var eventData = [];
24.317 + if (isClickOnCheckableElement(element, eventType)) {
24.318 + // Work around the jQuery "click events on checkboxes" issue described above by storing the original checked state before triggering the handler
24.319 + eventData.push({ checkedStateBeforeEvent: element.checked });
24.320 + }
24.321 + jQuery(element)['trigger'](eventType, eventData);
24.322 + } else if (typeof document.createEvent == "function") {
24.323 + if (typeof element.dispatchEvent == "function") {
24.324 + var eventCategory = knownEventTypesByEventName[eventType] || "HTMLEvents";
24.325 + var event = document.createEvent(eventCategory);
24.326 + event.initEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
24.327 + element.dispatchEvent(event);
24.328 + }
24.329 + else
24.330 + throw new Error("The supplied element doesn't support dispatchEvent");
24.331 + } else if (typeof element.fireEvent != "undefined") {
24.332 + // Unlike other browsers, IE doesn't change the checked state of checkboxes/radiobuttons when you trigger their "click" event
24.333 + // so to make it consistent, we'll do it manually here
24.334 + if (isClickOnCheckableElement(element, eventType))
24.335 + element.checked = element.checked !== true;
24.336 + element.fireEvent("on" + eventType);
24.337 + }
24.338 + else
24.339 + throw new Error("Browser doesn't support triggering events");
24.340 + },
24.341 +
24.342 + unwrapObservable: function (value) {
24.343 + return ko.isObservable(value) ? value() : value;
24.344 + },
24.345 +
24.346 + peekObservable: function (value) {
24.347 + return ko.isObservable(value) ? value.peek() : value;
24.348 + },
24.349 +
24.350 + toggleDomNodeCssClass: function (node, classNames, shouldHaveClass) {
24.351 + if (classNames) {
24.352 + var cssClassNameRegex = /[\w-]+/g,
24.353 + currentClassNames = node.className.match(cssClassNameRegex) || [];
24.354 + ko.utils.arrayForEach(classNames.match(cssClassNameRegex), function(className) {
24.355 + var indexOfClass = ko.utils.arrayIndexOf(currentClassNames, className);
24.356 + if (indexOfClass >= 0) {
24.357 + if (!shouldHaveClass)
24.358 + currentClassNames.splice(indexOfClass, 1);
24.359 + } else {
24.360 + if (shouldHaveClass)
24.361 + currentClassNames.push(className);
24.362 + }
24.363 + });
24.364 + node.className = currentClassNames.join(" ");
24.365 + }
24.366 + },
24.367 +
24.368 + setTextContent: function(element, textContent) {
24.369 + var value = ko.utils.unwrapObservable(textContent);
24.370 + if ((value === null) || (value === undefined))
24.371 + value = "";
24.372 +
24.373 + if (element.nodeType === 3) {
24.374 + element.data = value;
24.375 + } else {
24.376 + // We need there to be exactly one child: a text node.
24.377 + // If there are no children, more than one, or if it's not a text node,
24.378 + // we'll clear everything and create a single text node.
24.379 + var innerTextNode = ko.virtualElements.firstChild(element);
24.380 + if (!innerTextNode || innerTextNode.nodeType != 3 || ko.virtualElements.nextSibling(innerTextNode)) {
24.381 + ko.virtualElements.setDomNodeChildren(element, [document.createTextNode(value)]);
24.382 + } else {
24.383 + innerTextNode.data = value;
24.384 + }
24.385 +
24.386 + ko.utils.forceRefresh(element);
24.387 + }
24.388 + },
24.389 +
24.390 + setElementName: function(element, name) {
24.391 + element.name = name;
24.392 +
24.393 + // Workaround IE 6/7 issue
24.394 + // - https://github.com/SteveSanderson/knockout/issues/197
24.395 + // - http://www.matts411.com/post/setting_the_name_attribute_in_ie_dom/
24.396 + if (ieVersion <= 7) {
24.397 + try {
24.398 + element.mergeAttributes(document.createElement("<input name='" + element.name + "'/>"), false);
24.399 + }
24.400 + catch(e) {} // For IE9 with doc mode "IE9 Standards" and browser mode "IE9 Compatibility View"
24.401 + }
24.402 + },
24.403 +
24.404 + forceRefresh: function(node) {
24.405 + // Workaround for an IE9 rendering bug - https://github.com/SteveSanderson/knockout/issues/209
24.406 + if (ieVersion >= 9) {
24.407 + // For text nodes and comment nodes (most likely virtual elements), we will have to refresh the container
24.408 + var elem = node.nodeType == 1 ? node : node.parentNode;
24.409 + if (elem.style)
24.410 + elem.style.zoom = elem.style.zoom;
24.411 + }
24.412 + },
24.413 +
24.414 + ensureSelectElementIsRenderedCorrectly: function(selectElement) {
24.415 + // 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.
24.416 + // (See https://github.com/SteveSanderson/knockout/issues/312, http://stackoverflow.com/questions/5908494/select-only-shows-first-char-of-selected-option)
24.417 + if (ieVersion >= 9) {
24.418 + var originalWidth = selectElement.style.width;
24.419 + selectElement.style.width = 0;
24.420 + selectElement.style.width = originalWidth;
24.421 + }
24.422 + },
24.423 +
24.424 + range: function (min, max) {
24.425 + min = ko.utils.unwrapObservable(min);
24.426 + max = ko.utils.unwrapObservable(max);
24.427 + var result = [];
24.428 + for (var i = min; i <= max; i++)
24.429 + result.push(i);
24.430 + return result;
24.431 + },
24.432 +
24.433 + makeArray: function(arrayLikeObject) {
24.434 + var result = [];
24.435 + for (var i = 0, j = arrayLikeObject.length; i < j; i++) {
24.436 + result.push(arrayLikeObject[i]);
24.437 + };
24.438 + return result;
24.439 + },
24.440 +
24.441 + isIe6 : isIe6,
24.442 + isIe7 : isIe7,
24.443 + ieVersion : ieVersion,
24.444 +
24.445 + getFormFields: function(form, fieldName) {
24.446 + var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
24.447 + var isMatchingField = (typeof fieldName == 'string')
24.448 + ? function(field) { return field.name === fieldName }
24.449 + : function(field) { return fieldName.test(field.name) }; // Treat fieldName as regex or object containing predicate
24.450 + var matches = [];
24.451 + for (var i = fields.length - 1; i >= 0; i--) {
24.452 + if (isMatchingField(fields[i]))
24.453 + matches.push(fields[i]);
24.454 + };
24.455 + return matches;
24.456 + },
24.457 +
24.458 + parseJson: function (jsonString) {
24.459 + if (typeof jsonString == "string") {
24.460 + jsonString = ko.utils.stringTrim(jsonString);
24.461 + if (jsonString) {
24.462 + if (window.JSON && window.JSON.parse) // Use native parsing where available
24.463 + return window.JSON.parse(jsonString);
24.464 + return (new Function("return " + jsonString))(); // Fallback on less safe parsing for older browsers
24.465 + }
24.466 + }
24.467 + return null;
24.468 + },
24.469 +
24.470 + stringifyJson: function (data, replacer, space) { // replacer and space are optional
24.471 + if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined"))
24.472 + 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");
24.473 + return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
24.474 + },
24.475 +
24.476 + postJson: function (urlOrForm, data, options) {
24.477 + options = options || {};
24.478 + var params = options['params'] || {};
24.479 + var includeFields = options['includeFields'] || this.fieldsIncludedWithJsonPost;
24.480 + var url = urlOrForm;
24.481 +
24.482 + // If we were given a form, use its 'action' URL and pick out any requested field values
24.483 + if((typeof urlOrForm == 'object') && (ko.utils.tagNameLower(urlOrForm) === "form")) {
24.484 + var originalForm = urlOrForm;
24.485 + url = originalForm.action;
24.486 + for (var i = includeFields.length - 1; i >= 0; i--) {
24.487 + var fields = ko.utils.getFormFields(originalForm, includeFields[i]);
24.488 + for (var j = fields.length - 1; j >= 0; j--)
24.489 + params[fields[j].name] = fields[j].value;
24.490 + }
24.491 + }
24.492 +
24.493 + data = ko.utils.unwrapObservable(data);
24.494 + var form = document.createElement("form");
24.495 + form.style.display = "none";
24.496 + form.action = url;
24.497 + form.method = "post";
24.498 + for (var key in data) {
24.499 + var input = document.createElement("input");
24.500 + input.name = key;
24.501 + input.value = ko.utils.stringifyJson(ko.utils.unwrapObservable(data[key]));
24.502 + form.appendChild(input);
24.503 + }
24.504 + for (var key in params) {
24.505 + var input = document.createElement("input");
24.506 + input.name = key;
24.507 + input.value = params[key];
24.508 + form.appendChild(input);
24.509 + }
24.510 + document.body.appendChild(form);
24.511 + options['submitter'] ? options['submitter'](form) : form.submit();
24.512 + setTimeout(function () { form.parentNode.removeChild(form); }, 0);
24.513 + }
24.514 + }
24.515 +})();
24.516 +
24.517 +ko.exportSymbol('utils', ko.utils);
24.518 +ko.exportSymbol('utils.arrayForEach', ko.utils.arrayForEach);
24.519 +ko.exportSymbol('utils.arrayFirst', ko.utils.arrayFirst);
24.520 +ko.exportSymbol('utils.arrayFilter', ko.utils.arrayFilter);
24.521 +ko.exportSymbol('utils.arrayGetDistinctValues', ko.utils.arrayGetDistinctValues);
24.522 +ko.exportSymbol('utils.arrayIndexOf', ko.utils.arrayIndexOf);
24.523 +ko.exportSymbol('utils.arrayMap', ko.utils.arrayMap);
24.524 +ko.exportSymbol('utils.arrayPushAll', ko.utils.arrayPushAll);
24.525 +ko.exportSymbol('utils.arrayRemoveItem', ko.utils.arrayRemoveItem);
24.526 +ko.exportSymbol('utils.extend', ko.utils.extend);
24.527 +ko.exportSymbol('utils.fieldsIncludedWithJsonPost', ko.utils.fieldsIncludedWithJsonPost);
24.528 +ko.exportSymbol('utils.getFormFields', ko.utils.getFormFields);
24.529 +ko.exportSymbol('utils.peekObservable', ko.utils.peekObservable);
24.530 +ko.exportSymbol('utils.postJson', ko.utils.postJson);
24.531 +ko.exportSymbol('utils.parseJson', ko.utils.parseJson);
24.532 +ko.exportSymbol('utils.registerEventHandler', ko.utils.registerEventHandler);
24.533 +ko.exportSymbol('utils.stringifyJson', ko.utils.stringifyJson);
24.534 +ko.exportSymbol('utils.range', ko.utils.range);
24.535 +ko.exportSymbol('utils.toggleDomNodeCssClass', ko.utils.toggleDomNodeCssClass);
24.536 +ko.exportSymbol('utils.triggerEvent', ko.utils.triggerEvent);
24.537 +ko.exportSymbol('utils.unwrapObservable', ko.utils.unwrapObservable);
24.538 +
24.539 +if (!Function.prototype['bind']) {
24.540 + // 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)
24.541 + // In case the browser doesn't implement it natively, provide a JavaScript implementation. This implementation is based on the one in prototype.js
24.542 + Function.prototype['bind'] = function (object) {
24.543 + var originalFunction = this, args = Array.prototype.slice.call(arguments), object = args.shift();
24.544 + return function () {
24.545 + return originalFunction.apply(object, args.concat(Array.prototype.slice.call(arguments)));
24.546 + };
24.547 + };
24.548 +}
24.549 +
24.550 +ko.utils.domData = new (function () {
24.551 + var uniqueId = 0;
24.552 + var dataStoreKeyExpandoPropertyName = "__ko__" + (new Date).getTime();
24.553 + var dataStore = {};
24.554 + return {
24.555 + get: function (node, key) {
24.556 + var allDataForNode = ko.utils.domData.getAll(node, false);
24.557 + return allDataForNode === undefined ? undefined : allDataForNode[key];
24.558 + },
24.559 + set: function (node, key, value) {
24.560 + if (value === undefined) {
24.561 + // Make sure we don't actually create a new domData key if we are actually deleting a value
24.562 + if (ko.utils.domData.getAll(node, false) === undefined)
24.563 + return;
24.564 + }
24.565 + var allDataForNode = ko.utils.domData.getAll(node, true);
24.566 + allDataForNode[key] = value;
24.567 + },
24.568 + getAll: function (node, createIfNotFound) {
24.569 + var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
24.570 + var hasExistingDataStore = dataStoreKey && (dataStoreKey !== "null") && dataStore[dataStoreKey];
24.571 + if (!hasExistingDataStore) {
24.572 + if (!createIfNotFound)
24.573 + return undefined;
24.574 + dataStoreKey = node[dataStoreKeyExpandoPropertyName] = "ko" + uniqueId++;
24.575 + dataStore[dataStoreKey] = {};
24.576 + }
24.577 + return dataStore[dataStoreKey];
24.578 + },
24.579 + clear: function (node) {
24.580 + var dataStoreKey = node[dataStoreKeyExpandoPropertyName];
24.581 + if (dataStoreKey) {
24.582 + delete dataStore[dataStoreKey];
24.583 + node[dataStoreKeyExpandoPropertyName] = null;
24.584 + return true; // Exposing "did clean" flag purely so specs can infer whether things have been cleaned up as intended
24.585 + }
24.586 + return false;
24.587 + }
24.588 + }
24.589 +})();
24.590 +
24.591 +ko.exportSymbol('utils.domData', ko.utils.domData);
24.592 +ko.exportSymbol('utils.domData.clear', ko.utils.domData.clear); // Exporting only so specs can clear up after themselves fully
24.593 +
24.594 +ko.utils.domNodeDisposal = new (function () {
24.595 + var domDataKey = "__ko_domNodeDisposal__" + (new Date).getTime();
24.596 + var cleanableNodeTypes = { 1: true, 8: true, 9: true }; // Element, Comment, Document
24.597 + var cleanableNodeTypesWithDescendants = { 1: true, 9: true }; // Element, Document
24.598 +
24.599 + function getDisposeCallbacksCollection(node, createIfNotFound) {
24.600 + var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);
24.601 + if ((allDisposeCallbacks === undefined) && createIfNotFound) {
24.602 + allDisposeCallbacks = [];
24.603 + ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);
24.604 + }
24.605 + return allDisposeCallbacks;
24.606 + }
24.607 + function destroyCallbacksCollection(node) {
24.608 + ko.utils.domData.set(node, domDataKey, undefined);
24.609 + }
24.610 +
24.611 + function cleanSingleNode(node) {
24.612 + // Run all the dispose callbacks
24.613 + var callbacks = getDisposeCallbacksCollection(node, false);
24.614 + if (callbacks) {
24.615 + callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
24.616 + for (var i = 0; i < callbacks.length; i++)
24.617 + callbacks[i](node);
24.618 + }
24.619 +
24.620 + // Also erase the DOM data
24.621 + ko.utils.domData.clear(node);
24.622 +
24.623 + // Special support for jQuery here because it's so commonly used.
24.624 + // Many jQuery plugins (including jquery.tmpl) store data using jQuery's equivalent of domData
24.625 + // so notify it to tear down any resources associated with the node & descendants here.
24.626 + if ((typeof jQuery == "function") && (typeof jQuery['cleanData'] == "function"))
24.627 + jQuery['cleanData']([node]);
24.628 +
24.629 + // Also clear any immediate-child comment nodes, as these wouldn't have been found by
24.630 + // node.getElementsByTagName("*") in cleanNode() (comment nodes aren't elements)
24.631 + if (cleanableNodeTypesWithDescendants[node.nodeType])
24.632 + cleanImmediateCommentTypeChildren(node);
24.633 + }
24.634 +
24.635 + function cleanImmediateCommentTypeChildren(nodeWithChildren) {
24.636 + var child, nextChild = nodeWithChildren.firstChild;
24.637 + while (child = nextChild) {
24.638 + nextChild = child.nextSibling;
24.639 + if (child.nodeType === 8)
24.640 + cleanSingleNode(child);
24.641 + }
24.642 + }
24.643 +
24.644 + return {
24.645 + addDisposeCallback : function(node, callback) {
24.646 + if (typeof callback != "function")
24.647 + throw new Error("Callback must be a function");
24.648 + getDisposeCallbacksCollection(node, true).push(callback);
24.649 + },
24.650 +
24.651 + removeDisposeCallback : function(node, callback) {
24.652 + var callbacksCollection = getDisposeCallbacksCollection(node, false);
24.653 + if (callbacksCollection) {
24.654 + ko.utils.arrayRemoveItem(callbacksCollection, callback);
24.655 + if (callbacksCollection.length == 0)
24.656 + destroyCallbacksCollection(node);
24.657 + }
24.658 + },
24.659 +
24.660 + cleanNode : function(node) {
24.661 + // First clean this node, where applicable
24.662 + if (cleanableNodeTypes[node.nodeType]) {
24.663 + cleanSingleNode(node);
24.664 +
24.665 + // ... then its descendants, where applicable
24.666 + if (cleanableNodeTypesWithDescendants[node.nodeType]) {
24.667 + // Clone the descendants list in case it changes during iteration
24.668 + var descendants = [];
24.669 + ko.utils.arrayPushAll(descendants, node.getElementsByTagName("*"));
24.670 + for (var i = 0, j = descendants.length; i < j; i++)
24.671 + cleanSingleNode(descendants[i]);
24.672 + }
24.673 + }
24.674 + return node;
24.675 + },
24.676 +
24.677 + removeNode : function(node) {
24.678 + ko.cleanNode(node);
24.679 + if (node.parentNode)
24.680 + node.parentNode.removeChild(node);
24.681 + }
24.682 + }
24.683 +})();
24.684 +ko.cleanNode = ko.utils.domNodeDisposal.cleanNode; // Shorthand name for convenience
24.685 +ko.removeNode = ko.utils.domNodeDisposal.removeNode; // Shorthand name for convenience
24.686 +ko.exportSymbol('cleanNode', ko.cleanNode);
24.687 +ko.exportSymbol('removeNode', ko.removeNode);
24.688 +ko.exportSymbol('utils.domNodeDisposal', ko.utils.domNodeDisposal);
24.689 +ko.exportSymbol('utils.domNodeDisposal.addDisposeCallback', ko.utils.domNodeDisposal.addDisposeCallback);
24.690 +ko.exportSymbol('utils.domNodeDisposal.removeDisposeCallback', ko.utils.domNodeDisposal.removeDisposeCallback);
24.691 +(function () {
24.692 + var leadingCommentRegex = /^(\s*)<!--(.*?)-->/;
24.693 +
24.694 + function simpleHtmlParse(html) {
24.695 + // Based on jQuery's "clean" function, but only accounting for table-related elements.
24.696 + // If you have referenced jQuery, this won't be used anyway - KO will use jQuery's "clean" function directly
24.697 +
24.698 + // Note that there's still an issue in IE < 9 whereby it will discard comment nodes that are the first child of
24.699 + // a descendant node. For example: "<div><!-- mycomment -->abc</div>" will get parsed as "<div>abc</div>"
24.700 + // This won't affect anyone who has referenced jQuery, and there's always the workaround of inserting a dummy node
24.701 + // (possibly a text node) in front of the comment. So, KO does not attempt to workaround this IE issue automatically at present.
24.702 +
24.703 + // Trim whitespace, otherwise indexOf won't work as expected
24.704 + var tags = ko.utils.stringTrim(html).toLowerCase(), div = document.createElement("div");
24.705 +
24.706 + // Finds the first match from the left column, and returns the corresponding "wrap" data from the right column
24.707 + var wrap = tags.match(/^<(thead|tbody|tfoot)/) && [1, "<table>", "</table>"] ||
24.708 + !tags.indexOf("<tr") && [2, "<table><tbody>", "</tbody></table>"] ||
24.709 + (!tags.indexOf("<td") || !tags.indexOf("<th")) && [3, "<table><tbody><tr>", "</tr></tbody></table>"] ||
24.710 + /* anything else */ [0, "", ""];
24.711 +
24.712 + // Go to html and back, then peel off extra wrappers
24.713 + // Note that we always prefix with some dummy text, because otherwise, IE<9 will strip out leading comment nodes in descendants. Total madness.
24.714 + var markup = "ignored<div>" + wrap[1] + html + wrap[2] + "</div>";
24.715 + if (typeof window['innerShiv'] == "function") {
24.716 + div.appendChild(window['innerShiv'](markup));
24.717 + } else {
24.718 + div.innerHTML = markup;
24.719 + }
24.720 +
24.721 + // Move to the right depth
24.722 + while (wrap[0]--)
24.723 + div = div.lastChild;
24.724 +
24.725 + return ko.utils.makeArray(div.lastChild.childNodes);
24.726 + }
24.727 +
24.728 + function jQueryHtmlParse(html) {
24.729 + // jQuery's "parseHTML" function was introduced in jQuery 1.8.0 and is a documented public API.
24.730 + if (jQuery['parseHTML']) {
24.731 + return jQuery['parseHTML'](html);
24.732 + } else {
24.733 + // For jQuery < 1.8.0, we fall back on the undocumented internal "clean" function.
24.734 + var elems = jQuery['clean']([html]);
24.735 +
24.736 + // 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.
24.737 + // Unfortunately, it never clears the dummy parent nodes from the document fragment, so it leaks memory over time.
24.738 + // Fix this by finding the top-most dummy parent element, and detaching it from its owner fragment.
24.739 + if (elems && elems[0]) {
24.740 + // Find the top-most parent element that's a direct child of a document fragment
24.741 + var elem = elems[0];
24.742 + while (elem.parentNode && elem.parentNode.nodeType !== 11 /* i.e., DocumentFragment */)
24.743 + elem = elem.parentNode;
24.744 + // ... then detach it
24.745 + if (elem.parentNode)
24.746 + elem.parentNode.removeChild(elem);
24.747 + }
24.748 +
24.749 + return elems;
24.750 + }
24.751 + }
24.752 +
24.753 + ko.utils.parseHtmlFragment = function(html) {
24.754 + return typeof jQuery != 'undefined' ? jQueryHtmlParse(html) // As below, benefit from jQuery's optimisations where possible
24.755 + : simpleHtmlParse(html); // ... otherwise, this simple logic will do in most common cases.
24.756 + };
24.757 +
24.758 + ko.utils.setHtml = function(node, html) {
24.759 + ko.utils.emptyDomNode(node);
24.760 +
24.761 + // There's no legitimate reason to display a stringified observable without unwrapping it, so we'll unwrap it
24.762 + html = ko.utils.unwrapObservable(html);
24.763 +
24.764 + if ((html !== null) && (html !== undefined)) {
24.765 + if (typeof html != 'string')
24.766 + html = html.toString();
24.767 +
24.768 + // jQuery contains a lot of sophisticated code to parse arbitrary HTML fragments,
24.769 + // for example <tr> elements which are not normally allowed to exist on their own.
24.770 + // If you've referenced jQuery we'll use that rather than duplicating its code.
24.771 + if (typeof jQuery != 'undefined') {
24.772 + jQuery(node)['html'](html);
24.773 + } else {
24.774 + // ... otherwise, use KO's own parsing logic.
24.775 + var parsedNodes = ko.utils.parseHtmlFragment(html);
24.776 + for (var i = 0; i < parsedNodes.length; i++)
24.777 + node.appendChild(parsedNodes[i]);
24.778 + }
24.779 + }
24.780 + };
24.781 +})();
24.782 +
24.783 +ko.exportSymbol('utils.parseHtmlFragment', ko.utils.parseHtmlFragment);
24.784 +ko.exportSymbol('utils.setHtml', ko.utils.setHtml);
24.785 +
24.786 +ko.memoization = (function () {
24.787 + var memos = {};
24.788 +
24.789 + function randomMax8HexChars() {
24.790 + return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
24.791 + }
24.792 + function generateRandomId() {
24.793 + return randomMax8HexChars() + randomMax8HexChars();
24.794 + }
24.795 + function findMemoNodes(rootNode, appendToArray) {
24.796 + if (!rootNode)
24.797 + return;
24.798 + if (rootNode.nodeType == 8) {
24.799 + var memoId = ko.memoization.parseMemoText(rootNode.nodeValue);
24.800 + if (memoId != null)
24.801 + appendToArray.push({ domNode: rootNode, memoId: memoId });
24.802 + } else if (rootNode.nodeType == 1) {
24.803 + for (var i = 0, childNodes = rootNode.childNodes, j = childNodes.length; i < j; i++)
24.804 + findMemoNodes(childNodes[i], appendToArray);
24.805 + }
24.806 + }
24.807 +
24.808 + return {
24.809 + memoize: function (callback) {
24.810 + if (typeof callback != "function")
24.811 + throw new Error("You can only pass a function to ko.memoization.memoize()");
24.812 + var memoId = generateRandomId();
24.813 + memos[memoId] = callback;
24.814 + return "<!--[ko_memo:" + memoId + "]-->";
24.815 + },
24.816 +
24.817 + unmemoize: function (memoId, callbackParams) {
24.818 + var callback = memos[memoId];
24.819 + if (callback === undefined)
24.820 + throw new Error("Couldn't find any memo with ID " + memoId + ". Perhaps it's already been unmemoized.");
24.821 + try {
24.822 + callback.apply(null, callbackParams || []);
24.823 + return true;
24.824 + }
24.825 + finally { delete memos[memoId]; }
24.826 + },
24.827 +
24.828 + unmemoizeDomNodeAndDescendants: function (domNode, extraCallbackParamsArray) {
24.829 + var memos = [];
24.830 + findMemoNodes(domNode, memos);
24.831 + for (var i = 0, j = memos.length; i < j; i++) {
24.832 + var node = memos[i].domNode;
24.833 + var combinedParams = [node];
24.834 + if (extraCallbackParamsArray)
24.835 + ko.utils.arrayPushAll(combinedParams, extraCallbackParamsArray);
24.836 + ko.memoization.unmemoize(memos[i].memoId, combinedParams);
24.837 + node.nodeValue = ""; // Neuter this node so we don't try to unmemoize it again
24.838 + if (node.parentNode)
24.839 + 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)
24.840 + }
24.841 + },
24.842 +
24.843 + parseMemoText: function (memoText) {
24.844 + var match = memoText.match(/^\[ko_memo\:(.*?)\]$/);
24.845 + return match ? match[1] : null;
24.846 + }
24.847 + };
24.848 +})();
24.849 +
24.850 +ko.exportSymbol('memoization', ko.memoization);
24.851 +ko.exportSymbol('memoization.memoize', ko.memoization.memoize);
24.852 +ko.exportSymbol('memoization.unmemoize', ko.memoization.unmemoize);
24.853 +ko.exportSymbol('memoization.parseMemoText', ko.memoization.parseMemoText);
24.854 +ko.exportSymbol('memoization.unmemoizeDomNodeAndDescendants', ko.memoization.unmemoizeDomNodeAndDescendants);
24.855 +ko.extenders = {
24.856 + 'throttle': function(target, timeout) {
24.857 + // Throttling means two things:
24.858 +
24.859 + // (1) For dependent observables, we throttle *evaluations* so that, no matter how fast its dependencies
24.860 + // notify updates, the target doesn't re-evaluate (and hence doesn't notify) faster than a certain rate
24.861 + target['throttleEvaluation'] = timeout;
24.862 +
24.863 + // (2) For writable targets (observables, or writable dependent observables), we throttle *writes*
24.864 + // so the target cannot change value synchronously or faster than a certain rate
24.865 + var writeTimeoutInstance = null;
24.866 + return ko.dependentObservable({
24.867 + 'read': target,
24.868 + 'write': function(value) {
24.869 + clearTimeout(writeTimeoutInstance);
24.870 + writeTimeoutInstance = setTimeout(function() {
24.871 + target(value);
24.872 + }, timeout);
24.873 + }
24.874 + });
24.875 + },
24.876 +
24.877 + 'notify': function(target, notifyWhen) {
24.878 + target["equalityComparer"] = notifyWhen == "always"
24.879 + ? function() { return false } // Treat all values as not equal
24.880 + : ko.observable["fn"]["equalityComparer"];
24.881 + return target;
24.882 + }
24.883 +};
24.884 +
24.885 +function applyExtenders(requestedExtenders) {
24.886 + var target = this;
24.887 + if (requestedExtenders) {
24.888 + for (var key in requestedExtenders) {
24.889 + var extenderHandler = ko.extenders[key];
24.890 + if (typeof extenderHandler == 'function') {
24.891 + target = extenderHandler(target, requestedExtenders[key]);
24.892 + }
24.893 + }
24.894 + }
24.895 + return target;
24.896 +}
24.897 +
24.898 +ko.exportSymbol('extenders', ko.extenders);
24.899 +
24.900 +ko.subscription = function (target, callback, disposeCallback) {
24.901 + this.target = target;
24.902 + this.callback = callback;
24.903 + this.disposeCallback = disposeCallback;
24.904 + ko.exportProperty(this, 'dispose', this.dispose);
24.905 +};
24.906 +ko.subscription.prototype.dispose = function () {
24.907 + this.isDisposed = true;
24.908 + this.disposeCallback();
24.909 +};
24.910 +
24.911 +ko.subscribable = function () {
24.912 + this._subscriptions = {};
24.913 +
24.914 + ko.utils.extend(this, ko.subscribable['fn']);
24.915 + ko.exportProperty(this, 'subscribe', this.subscribe);
24.916 + ko.exportProperty(this, 'extend', this.extend);
24.917 + ko.exportProperty(this, 'getSubscriptionsCount', this.getSubscriptionsCount);
24.918 +}
24.919 +
24.920 +var defaultEvent = "change";
24.921 +
24.922 +ko.subscribable['fn'] = {
24.923 + subscribe: function (callback, callbackTarget, event) {
24.924 + event = event || defaultEvent;
24.925 + var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
24.926 +
24.927 + var subscription = new ko.subscription(this, boundCallback, function () {
24.928 + ko.utils.arrayRemoveItem(this._subscriptions[event], subscription);
24.929 + }.bind(this));
24.930 +
24.931 + if (!this._subscriptions[event])
24.932 + this._subscriptions[event] = [];
24.933 + this._subscriptions[event].push(subscription);
24.934 + return subscription;
24.935 + },
24.936 +
24.937 + "notifySubscribers": function (valueToNotify, event) {
24.938 + event = event || defaultEvent;
24.939 + if (this._subscriptions[event]) {
24.940 + ko.dependencyDetection.ignore(function() {
24.941 + ko.utils.arrayForEach(this._subscriptions[event].slice(0), function (subscription) {
24.942 + // In case a subscription was disposed during the arrayForEach cycle, check
24.943 + // for isDisposed on each subscription before invoking its callback
24.944 + if (subscription && (subscription.isDisposed !== true))
24.945 + subscription.callback(valueToNotify);
24.946 + });
24.947 + }, this);
24.948 + }
24.949 + },
24.950 +
24.951 + getSubscriptionsCount: function () {
24.952 + var total = 0;
24.953 + for (var eventName in this._subscriptions) {
24.954 + if (this._subscriptions.hasOwnProperty(eventName))
24.955 + total += this._subscriptions[eventName].length;
24.956 + }
24.957 + return total;
24.958 + },
24.959 +
24.960 + extend: applyExtenders
24.961 +};
24.962 +
24.963 +
24.964 +ko.isSubscribable = function (instance) {
24.965 + return typeof instance.subscribe == "function" && typeof instance["notifySubscribers"] == "function";
24.966 +};
24.967 +
24.968 +ko.exportSymbol('subscribable', ko.subscribable);
24.969 +ko.exportSymbol('isSubscribable', ko.isSubscribable);
24.970 +
24.971 +ko.dependencyDetection = (function () {
24.972 + var _frames = [];
24.973 +
24.974 + return {
24.975 + begin: function (callback) {
24.976 + _frames.push({ callback: callback, distinctDependencies:[] });
24.977 + },
24.978 +
24.979 + end: function () {
24.980 + _frames.pop();
24.981 + },
24.982 +
24.983 + registerDependency: function (subscribable) {
24.984 + if (!ko.isSubscribable(subscribable))
24.985 + throw new Error("Only subscribable things can act as dependencies");
24.986 + if (_frames.length > 0) {
24.987 + var topFrame = _frames[_frames.length - 1];
24.988 + if (!topFrame || ko.utils.arrayIndexOf(topFrame.distinctDependencies, subscribable) >= 0)
24.989 + return;
24.990 + topFrame.distinctDependencies.push(subscribable);
24.991 + topFrame.callback(subscribable);
24.992 + }
24.993 + },
24.994 +
24.995 + ignore: function(callback, callbackTarget, callbackArgs) {
24.996 + try {
24.997 + _frames.push(null);
24.998 + return callback.apply(callbackTarget, callbackArgs || []);
24.999 + } finally {
24.1000 + _frames.pop();
24.1001 + }
24.1002 + }
24.1003 + };
24.1004 +})();
24.1005 +var primitiveTypes = { 'undefined':true, 'boolean':true, 'number':true, 'string':true };
24.1006 +
24.1007 +ko.observable = function (initialValue) {
24.1008 + var _latestValue = initialValue;
24.1009 +
24.1010 + function observable() {
24.1011 + if (arguments.length > 0) {
24.1012 + // Write
24.1013 +
24.1014 + // Ignore writes if the value hasn't changed
24.1015 + if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
24.1016 + observable.valueWillMutate();
24.1017 + _latestValue = arguments[0];
24.1018 + if (DEBUG) observable._latestValue = _latestValue;
24.1019 + observable.valueHasMutated();
24.1020 + }
24.1021 + return this; // Permits chained assignments
24.1022 + }
24.1023 + else {
24.1024 + // Read
24.1025 + ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
24.1026 + return _latestValue;
24.1027 + }
24.1028 + }
24.1029 + if (DEBUG) observable._latestValue = _latestValue;
24.1030 + ko.subscribable.call(observable);
24.1031 + observable.peek = function() { return _latestValue };
24.1032 + observable.valueHasMutated = function () { observable["notifySubscribers"](_latestValue); }
24.1033 + observable.valueWillMutate = function () { observable["notifySubscribers"](_latestValue, "beforeChange"); }
24.1034 + ko.utils.extend(observable, ko.observable['fn']);
24.1035 +
24.1036 + ko.exportProperty(observable, 'peek', observable.peek);
24.1037 + ko.exportProperty(observable, "valueHasMutated", observable.valueHasMutated);
24.1038 + ko.exportProperty(observable, "valueWillMutate", observable.valueWillMutate);
24.1039 +
24.1040 + return observable;
24.1041 +}
24.1042 +
24.1043 +ko.observable['fn'] = {
24.1044 + "equalityComparer": function valuesArePrimitiveAndEqual(a, b) {
24.1045 + var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
24.1046 + return oldValueIsPrimitive ? (a === b) : false;
24.1047 + }
24.1048 +};
24.1049 +
24.1050 +var protoProperty = ko.observable.protoProperty = "__ko_proto__";
24.1051 +ko.observable['fn'][protoProperty] = ko.observable;
24.1052 +
24.1053 +ko.hasPrototype = function(instance, prototype) {
24.1054 + if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
24.1055 + if (instance[protoProperty] === prototype) return true;
24.1056 + return ko.hasPrototype(instance[protoProperty], prototype); // Walk the prototype chain
24.1057 +};
24.1058 +
24.1059 +ko.isObservable = function (instance) {
24.1060 + return ko.hasPrototype(instance, ko.observable);
24.1061 +}
24.1062 +ko.isWriteableObservable = function (instance) {
24.1063 + // Observable
24.1064 + if ((typeof instance == "function") && instance[protoProperty] === ko.observable)
24.1065 + return true;
24.1066 + // Writeable dependent observable
24.1067 + if ((typeof instance == "function") && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
24.1068 + return true;
24.1069 + // Anything else
24.1070 + return false;
24.1071 +}
24.1072 +
24.1073 +
24.1074 +ko.exportSymbol('observable', ko.observable);
24.1075 +ko.exportSymbol('isObservable', ko.isObservable);
24.1076 +ko.exportSymbol('isWriteableObservable', ko.isWriteableObservable);
24.1077 +ko.observableArray = function (initialValues) {
24.1078 + if (arguments.length == 0) {
24.1079 + // Zero-parameter constructor initializes to empty array
24.1080 + initialValues = [];
24.1081 + }
24.1082 + if ((initialValues !== null) && (initialValues !== undefined) && !('length' in initialValues))
24.1083 + throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");
24.1084 +
24.1085 + var result = ko.observable(initialValues);
24.1086 + ko.utils.extend(result, ko.observableArray['fn']);
24.1087 + return result;
24.1088 +}
24.1089 +
24.1090 +ko.observableArray['fn'] = {
24.1091 + 'remove': function (valueOrPredicate) {
24.1092 + var underlyingArray = this.peek();
24.1093 + var removedValues = [];
24.1094 + var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
24.1095 + for (var i = 0; i < underlyingArray.length; i++) {
24.1096 + var value = underlyingArray[i];
24.1097 + if (predicate(value)) {
24.1098 + if (removedValues.length === 0) {
24.1099 + this.valueWillMutate();
24.1100 + }
24.1101 + removedValues.push(value);
24.1102 + underlyingArray.splice(i, 1);
24.1103 + i--;
24.1104 + }
24.1105 + }
24.1106 + if (removedValues.length) {
24.1107 + this.valueHasMutated();
24.1108 + }
24.1109 + return removedValues;
24.1110 + },
24.1111 +
24.1112 + 'removeAll': function (arrayOfValues) {
24.1113 + // If you passed zero args, we remove everything
24.1114 + if (arrayOfValues === undefined) {
24.1115 + var underlyingArray = this.peek();
24.1116 + var allValues = underlyingArray.slice(0);
24.1117 + this.valueWillMutate();
24.1118 + underlyingArray.splice(0, underlyingArray.length);
24.1119 + this.valueHasMutated();
24.1120 + return allValues;
24.1121 + }
24.1122 + // If you passed an arg, we interpret it as an array of entries to remove
24.1123 + if (!arrayOfValues)
24.1124 + return [];
24.1125 + return this['remove'](function (value) {
24.1126 + return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
24.1127 + });
24.1128 + },
24.1129 +
24.1130 + 'destroy': function (valueOrPredicate) {
24.1131 + var underlyingArray = this.peek();
24.1132 + var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
24.1133 + this.valueWillMutate();
24.1134 + for (var i = underlyingArray.length - 1; i >= 0; i--) {
24.1135 + var value = underlyingArray[i];
24.1136 + if (predicate(value))
24.1137 + underlyingArray[i]["_destroy"] = true;
24.1138 + }
24.1139 + this.valueHasMutated();
24.1140 + },
24.1141 +
24.1142 + 'destroyAll': function (arrayOfValues) {
24.1143 + // If you passed zero args, we destroy everything
24.1144 + if (arrayOfValues === undefined)
24.1145 + return this['destroy'](function() { return true });
24.1146 +
24.1147 + // If you passed an arg, we interpret it as an array of entries to destroy
24.1148 + if (!arrayOfValues)
24.1149 + return [];
24.1150 + return this['destroy'](function (value) {
24.1151 + return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
24.1152 + });
24.1153 + },
24.1154 +
24.1155 + 'indexOf': function (item) {
24.1156 + var underlyingArray = this();
24.1157 + return ko.utils.arrayIndexOf(underlyingArray, item);
24.1158 + },
24.1159 +
24.1160 + 'replace': function(oldItem, newItem) {
24.1161 + var index = this['indexOf'](oldItem);
24.1162 + if (index >= 0) {
24.1163 + this.valueWillMutate();
24.1164 + this.peek()[index] = newItem;
24.1165 + this.valueHasMutated();
24.1166 + }
24.1167 + }
24.1168 +}
24.1169 +
24.1170 +// Populate ko.observableArray.fn with read/write functions from native arrays
24.1171 +// Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
24.1172 +// because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale
24.1173 +ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function (methodName) {
24.1174 + ko.observableArray['fn'][methodName] = function () {
24.1175 + // Use "peek" to avoid creating a subscription in any computed that we're executing in the context of
24.1176 + // (for consistency with mutating regular observables)
24.1177 + var underlyingArray = this.peek();
24.1178 + this.valueWillMutate();
24.1179 + var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
24.1180 + this.valueHasMutated();
24.1181 + return methodCallResult;
24.1182 + };
24.1183 +});
24.1184 +
24.1185 +// Populate ko.observableArray.fn with read-only functions from native arrays
24.1186 +ko.utils.arrayForEach(["slice"], function (methodName) {
24.1187 + ko.observableArray['fn'][methodName] = function () {
24.1188 + var underlyingArray = this();
24.1189 + return underlyingArray[methodName].apply(underlyingArray, arguments);
24.1190 + };
24.1191 +});
24.1192 +
24.1193 +ko.exportSymbol('observableArray', ko.observableArray);
24.1194 +ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunctionTarget, options) {
24.1195 + var _latestValue,
24.1196 + _hasBeenEvaluated = false,
24.1197 + _isBeingEvaluated = false,
24.1198 + readFunction = evaluatorFunctionOrOptions;
24.1199 +
24.1200 + if (readFunction && typeof readFunction == "object") {
24.1201 + // Single-parameter syntax - everything is on this "options" param
24.1202 + options = readFunction;
24.1203 + readFunction = options["read"];
24.1204 + } else {
24.1205 + // Multi-parameter syntax - construct the options according to the params passed
24.1206 + options = options || {};
24.1207 + if (!readFunction)
24.1208 + readFunction = options["read"];
24.1209 + }
24.1210 + if (typeof readFunction != "function")
24.1211 + throw new Error("Pass a function that returns the value of the ko.computed");
24.1212 +
24.1213 + function addSubscriptionToDependency(subscribable) {
24.1214 + _subscriptionsToDependencies.push(subscribable.subscribe(evaluatePossiblyAsync));
24.1215 + }
24.1216 +
24.1217 + function disposeAllSubscriptionsToDependencies() {
24.1218 + ko.utils.arrayForEach(_subscriptionsToDependencies, function (subscription) {
24.1219 + subscription.dispose();
24.1220 + });
24.1221 + _subscriptionsToDependencies = [];
24.1222 + }
24.1223 +
24.1224 + function evaluatePossiblyAsync() {
24.1225 + var throttleEvaluationTimeout = dependentObservable['throttleEvaluation'];
24.1226 + if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
24.1227 + clearTimeout(evaluationTimeoutInstance);
24.1228 + evaluationTimeoutInstance = setTimeout(evaluateImmediate, throttleEvaluationTimeout);
24.1229 + } else
24.1230 + evaluateImmediate();
24.1231 + }
24.1232 +
24.1233 + function evaluateImmediate() {
24.1234 + if (_isBeingEvaluated) {
24.1235 + // If the evaluation of a ko.computed causes side effects, it's possible that it will trigger its own re-evaluation.
24.1236 + // This is not desirable (it's hard for a developer to realise a chain of dependencies might cause this, and they almost
24.1237 + // certainly didn't intend infinite re-evaluations). So, for predictability, we simply prevent ko.computeds from causing
24.1238 + // their own re-evaluation. Further discussion at https://github.com/SteveSanderson/knockout/pull/387
24.1239 + return;
24.1240 + }
24.1241 +
24.1242 + // Don't dispose on first evaluation, because the "disposeWhen" callback might
24.1243 + // e.g., dispose when the associated DOM element isn't in the doc, and it's not
24.1244 + // going to be in the doc until *after* the first evaluation
24.1245 + if (_hasBeenEvaluated && disposeWhen()) {
24.1246 + dispose();
24.1247 + return;
24.1248 + }
24.1249 +
24.1250 + _isBeingEvaluated = true;
24.1251 + try {
24.1252 + // Initially, we assume that none of the subscriptions are still being used (i.e., all are candidates for disposal).
24.1253 + // Then, during evaluation, we cross off any that are in fact still being used.
24.1254 + var disposalCandidates = ko.utils.arrayMap(_subscriptionsToDependencies, function(item) {return item.target;});
24.1255 +
24.1256 + ko.dependencyDetection.begin(function(subscribable) {
24.1257 + var inOld;
24.1258 + if ((inOld = ko.utils.arrayIndexOf(disposalCandidates, subscribable)) >= 0)
24.1259 + disposalCandidates[inOld] = undefined; // Don't want to dispose this subscription, as it's still being used
24.1260 + else
24.1261 + addSubscriptionToDependency(subscribable); // Brand new subscription - add it
24.1262 + });
24.1263 +
24.1264 + var newValue = readFunction.call(evaluatorFunctionTarget);
24.1265 +
24.1266 + // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
24.1267 + for (var i = disposalCandidates.length - 1; i >= 0; i--) {
24.1268 + if (disposalCandidates[i])
24.1269 + _subscriptionsToDependencies.splice(i, 1)[0].dispose();
24.1270 + }
24.1271 + _hasBeenEvaluated = true;
24.1272 +
24.1273 + dependentObservable["notifySubscribers"](_latestValue, "beforeChange");
24.1274 + _latestValue = newValue;
24.1275 + if (DEBUG) dependentObservable._latestValue = _latestValue;
24.1276 + } finally {
24.1277 + ko.dependencyDetection.end();
24.1278 + }
24.1279 +
24.1280 + dependentObservable["notifySubscribers"](_latestValue);
24.1281 + _isBeingEvaluated = false;
24.1282 + if (!_subscriptionsToDependencies.length)
24.1283 + dispose();
24.1284 + }
24.1285 +
24.1286 + function dependentObservable() {
24.1287 + if (arguments.length > 0) {
24.1288 + if (typeof writeFunction === "function") {
24.1289 + // Writing a value
24.1290 + writeFunction.apply(evaluatorFunctionTarget, arguments);
24.1291 + } else {
24.1292 + 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.");
24.1293 + }
24.1294 + return this; // Permits chained assignments
24.1295 + } else {
24.1296 + // Reading the value
24.1297 + if (!_hasBeenEvaluated)
24.1298 + evaluateImmediate();
24.1299 + ko.dependencyDetection.registerDependency(dependentObservable);
24.1300 + return _latestValue;
24.1301 + }
24.1302 + }
24.1303 +
24.1304 + function peek() {
24.1305 + if (!_hasBeenEvaluated)
24.1306 + evaluateImmediate();
24.1307 + return _latestValue;
24.1308 + }
24.1309 +
24.1310 + function isActive() {
24.1311 + return !_hasBeenEvaluated || _subscriptionsToDependencies.length > 0;
24.1312 + }
24.1313 +
24.1314 + // By here, "options" is always non-null
24.1315 + var writeFunction = options["write"],
24.1316 + disposeWhenNodeIsRemoved = options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
24.1317 + disposeWhen = options["disposeWhen"] || options.disposeWhen || function() { return false; },
24.1318 + dispose = disposeAllSubscriptionsToDependencies,
24.1319 + _subscriptionsToDependencies = [],
24.1320 + evaluationTimeoutInstance = null;
24.1321 +
24.1322 + if (!evaluatorFunctionTarget)
24.1323 + evaluatorFunctionTarget = options["owner"];
24.1324 +
24.1325 + dependentObservable.peek = peek;
24.1326 + dependentObservable.getDependenciesCount = function () { return _subscriptionsToDependencies.length; };
24.1327 + dependentObservable.hasWriteFunction = typeof options["write"] === "function";
24.1328 + dependentObservable.dispose = function () { dispose(); };
24.1329 + dependentObservable.isActive = isActive;
24.1330 + dependentObservable.valueHasMutated = function() {
24.1331 + _hasBeenEvaluated = false;
24.1332 + evaluateImmediate();
24.1333 + };
24.1334 +
24.1335 + ko.subscribable.call(dependentObservable);
24.1336 + ko.utils.extend(dependentObservable, ko.dependentObservable['fn']);
24.1337 +
24.1338 + ko.exportProperty(dependentObservable, 'peek', dependentObservable.peek);
24.1339 + ko.exportProperty(dependentObservable, 'dispose', dependentObservable.dispose);
24.1340 + ko.exportProperty(dependentObservable, 'isActive', dependentObservable.isActive);
24.1341 + ko.exportProperty(dependentObservable, 'getDependenciesCount', dependentObservable.getDependenciesCount);
24.1342 +
24.1343 + // Evaluate, unless deferEvaluation is true
24.1344 + if (options['deferEvaluation'] !== true)
24.1345 + evaluateImmediate();
24.1346 +
24.1347 + // Build "disposeWhenNodeIsRemoved" and "disposeWhenNodeIsRemovedCallback" option values.
24.1348 + // But skip if isActive is false (there will never be any dependencies to dispose).
24.1349 + // (Note: "disposeWhenNodeIsRemoved" option both proactively disposes as soon as the node is removed using ko.removeNode(),
24.1350 + // plus adds a "disposeWhen" callback that, on each evaluation, disposes if the node was removed by some other means.)
24.1351 + if (disposeWhenNodeIsRemoved && isActive()) {
24.1352 + dispose = function() {
24.1353 + ko.utils.domNodeDisposal.removeDisposeCallback(disposeWhenNodeIsRemoved, arguments.callee);
24.1354 + disposeAllSubscriptionsToDependencies();
24.1355 + };
24.1356 + ko.utils.domNodeDisposal.addDisposeCallback(disposeWhenNodeIsRemoved, dispose);
24.1357 + var existingDisposeWhenFunction = disposeWhen;
24.1358 + disposeWhen = function () {
24.1359 + return !ko.utils.domNodeIsAttachedToDocument(disposeWhenNodeIsRemoved) || existingDisposeWhenFunction();
24.1360 + }
24.1361 + }
24.1362 +
24.1363 + return dependentObservable;
24.1364 +};
24.1365 +
24.1366 +ko.isComputed = function(instance) {
24.1367 + return ko.hasPrototype(instance, ko.dependentObservable);
24.1368 +};
24.1369 +
24.1370 +var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
24.1371 +ko.dependentObservable[protoProp] = ko.observable;
24.1372 +
24.1373 +ko.dependentObservable['fn'] = {};
24.1374 +ko.dependentObservable['fn'][protoProp] = ko.dependentObservable;
24.1375 +
24.1376 +ko.exportSymbol('dependentObservable', ko.dependentObservable);
24.1377 +ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
24.1378 +ko.exportSymbol('isComputed', ko.isComputed);
24.1379 +
24.1380 +(function() {
24.1381 + var maxNestedObservableDepth = 10; // Escape the (unlikely) pathalogical case where an observable's current value is itself (or similar reference cycle)
24.1382 +
24.1383 + ko.toJS = function(rootObject) {
24.1384 + if (arguments.length == 0)
24.1385 + throw new Error("When calling ko.toJS, pass the object you want to convert.");
24.1386 +
24.1387 + // We just unwrap everything at every level in the object graph
24.1388 + return mapJsObjectGraph(rootObject, function(valueToMap) {
24.1389 + // Loop because an observable's value might in turn be another observable wrapper
24.1390 + for (var i = 0; ko.isObservable(valueToMap) && (i < maxNestedObservableDepth); i++)
24.1391 + valueToMap = valueToMap();
24.1392 + return valueToMap;
24.1393 + });
24.1394 + };
24.1395 +
24.1396 + ko.toJSON = function(rootObject, replacer, space) { // replacer and space are optional
24.1397 + var plainJavaScriptObject = ko.toJS(rootObject);
24.1398 + return ko.utils.stringifyJson(plainJavaScriptObject, replacer, space);
24.1399 + };
24.1400 +
24.1401 + function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
24.1402 + visitedObjects = visitedObjects || new objectLookup();
24.1403 +
24.1404 + rootObject = mapInputCallback(rootObject);
24.1405 + var canHaveProperties = (typeof rootObject == "object") && (rootObject !== null) && (rootObject !== undefined) && (!(rootObject instanceof Date));
24.1406 + if (!canHaveProperties)
24.1407 + return rootObject;
24.1408 +
24.1409 + var outputProperties = rootObject instanceof Array ? [] : {};
24.1410 + visitedObjects.save(rootObject, outputProperties);
24.1411 +
24.1412 + visitPropertiesOrArrayEntries(rootObject, function(indexer) {
24.1413 + var propertyValue = mapInputCallback(rootObject[indexer]);
24.1414 +
24.1415 + switch (typeof propertyValue) {
24.1416 + case "boolean":
24.1417 + case "number":
24.1418 + case "string":
24.1419 + case "function":
24.1420 + outputProperties[indexer] = propertyValue;
24.1421 + break;
24.1422 + case "object":
24.1423 + case "undefined":
24.1424 + var previouslyMappedValue = visitedObjects.get(propertyValue);
24.1425 + outputProperties[indexer] = (previouslyMappedValue !== undefined)
24.1426 + ? previouslyMappedValue
24.1427 + : mapJsObjectGraph(propertyValue, mapInputCallback, visitedObjects);
24.1428 + break;
24.1429 + }
24.1430 + });
24.1431 +
24.1432 + return outputProperties;
24.1433 + }
24.1434 +
24.1435 + function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
24.1436 + if (rootObject instanceof Array) {
24.1437 + for (var i = 0; i < rootObject.length; i++)
24.1438 + visitorCallback(i);
24.1439 +
24.1440 + // For arrays, also respect toJSON property for custom mappings (fixes #278)
24.1441 + if (typeof rootObject['toJSON'] == 'function')
24.1442 + visitorCallback('toJSON');
24.1443 + } else {
24.1444 + for (var propertyName in rootObject)
24.1445 + visitorCallback(propertyName);
24.1446 + }
24.1447 + };
24.1448 +
24.1449 + function objectLookup() {
24.1450 + var keys = [];
24.1451 + var values = [];
24.1452 + this.save = function(key, value) {
24.1453 + var existingIndex = ko.utils.arrayIndexOf(keys, key);
24.1454 + if (existingIndex >= 0)
24.1455 + values[existingIndex] = value;
24.1456 + else {
24.1457 + keys.push(key);
24.1458 + values.push(value);
24.1459 + }
24.1460 + };
24.1461 + this.get = function(key) {
24.1462 + var existingIndex = ko.utils.arrayIndexOf(keys, key);
24.1463 + return (existingIndex >= 0) ? values[existingIndex] : undefined;
24.1464 + };
24.1465 + };
24.1466 +})();
24.1467 +
24.1468 +ko.exportSymbol('toJS', ko.toJS);
24.1469 +ko.exportSymbol('toJSON', ko.toJSON);
24.1470 +(function () {
24.1471 + var hasDomDataExpandoProperty = '__ko__hasDomDataOptionValue__';
24.1472 +
24.1473 + // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
24.1474 + // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
24.1475 + // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
24.1476 + ko.selectExtensions = {
24.1477 + readValue : function(element) {
24.1478 + switch (ko.utils.tagNameLower(element)) {
24.1479 + case 'option':
24.1480 + if (element[hasDomDataExpandoProperty] === true)
24.1481 + return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
24.1482 + return ko.utils.ieVersion <= 7
24.1483 + ? (element.getAttributeNode('value').specified ? element.value : element.text)
24.1484 + : element.value;
24.1485 + case 'select':
24.1486 + return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
24.1487 + default:
24.1488 + return element.value;
24.1489 + }
24.1490 + },
24.1491 +
24.1492 + writeValue: function(element, value) {
24.1493 + switch (ko.utils.tagNameLower(element)) {
24.1494 + case 'option':
24.1495 + switch(typeof value) {
24.1496 + case "string":
24.1497 + ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
24.1498 + if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
24.1499 + delete element[hasDomDataExpandoProperty];
24.1500 + }
24.1501 + element.value = value;
24.1502 + break;
24.1503 + default:
24.1504 + // Store arbitrary object using DomData
24.1505 + ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
24.1506 + element[hasDomDataExpandoProperty] = true;
24.1507 +
24.1508 + // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
24.1509 + element.value = typeof value === "number" ? value : "";
24.1510 + break;
24.1511 + }
24.1512 + break;
24.1513 + case 'select':
24.1514 + for (var i = element.options.length - 1; i >= 0; i--) {
24.1515 + if (ko.selectExtensions.readValue(element.options[i]) == value) {
24.1516 + element.selectedIndex = i;
24.1517 + break;
24.1518 + }
24.1519 + }
24.1520 + break;
24.1521 + default:
24.1522 + if ((value === null) || (value === undefined))
24.1523 + value = "";
24.1524 + element.value = value;
24.1525 + break;
24.1526 + }
24.1527 + }
24.1528 + };
24.1529 +})();
24.1530 +
24.1531 +ko.exportSymbol('selectExtensions', ko.selectExtensions);
24.1532 +ko.exportSymbol('selectExtensions.readValue', ko.selectExtensions.readValue);
24.1533 +ko.exportSymbol('selectExtensions.writeValue', ko.selectExtensions.writeValue);
24.1534 +ko.expressionRewriting = (function () {
24.1535 + var restoreCapturedTokensRegex = /\@ko_token_(\d+)\@/g;
24.1536 + var javaScriptReservedWords = ["true", "false"];
24.1537 +
24.1538 + // Matches something that can be assigned to--either an isolated identifier or something ending with a property accessor
24.1539 + // This is designed to be simple and avoid false negatives, but could produce false positives (e.g., a+b.c).
24.1540 + var javaScriptAssignmentTarget = /^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i;
24.1541 +
24.1542 + function restoreTokens(string, tokens) {
24.1543 + var prevValue = null;
24.1544 + while (string != prevValue) { // Keep restoring tokens until it no longer makes a difference (they may be nested)
24.1545 + prevValue = string;
24.1546 + string = string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) {
24.1547 + return tokens[tokenIndex];
24.1548 + });
24.1549 + }
24.1550 + return string;
24.1551 + }
24.1552 +
24.1553 + function getWriteableValue(expression) {
24.1554 + if (ko.utils.arrayIndexOf(javaScriptReservedWords, ko.utils.stringTrim(expression).toLowerCase()) >= 0)
24.1555 + return false;
24.1556 + var match = expression.match(javaScriptAssignmentTarget);
24.1557 + return match === null ? false : match[1] ? ('Object(' + match[1] + ')' + match[2]) : expression;
24.1558 + }
24.1559 +
24.1560 + function ensureQuoted(key) {
24.1561 + var trimmedKey = ko.utils.stringTrim(key);
24.1562 + switch (trimmedKey.length && trimmedKey.charAt(0)) {
24.1563 + case "'":
24.1564 + case '"':
24.1565 + return key;
24.1566 + default:
24.1567 + return "'" + trimmedKey + "'";
24.1568 + }
24.1569 + }
24.1570 +
24.1571 + return {
24.1572 + bindingRewriteValidators: [],
24.1573 +
24.1574 + parseObjectLiteral: function(objectLiteralString) {
24.1575 + // A full tokeniser+lexer would add too much weight to this library, so here's a simple parser
24.1576 + // that is sufficient just to split an object literal string into a set of top-level key-value pairs
24.1577 +
24.1578 + var str = ko.utils.stringTrim(objectLiteralString);
24.1579 + if (str.length < 3)
24.1580 + return [];
24.1581 + if (str.charAt(0) === "{")// Ignore any braces surrounding the whole object literal
24.1582 + str = str.substring(1, str.length - 1);
24.1583 +
24.1584 + // Pull out any string literals and regex literals
24.1585 + var tokens = [];
24.1586 + var tokenStart = null, tokenEndChar;
24.1587 + for (var position = 0; position < str.length; position++) {
24.1588 + var c = str.charAt(position);
24.1589 + if (tokenStart === null) {
24.1590 + switch (c) {
24.1591 + case '"':
24.1592 + case "'":
24.1593 + case "/":
24.1594 + tokenStart = position;
24.1595 + tokenEndChar = c;
24.1596 + break;
24.1597 + }
24.1598 + } else if ((c == tokenEndChar) && (str.charAt(position - 1) !== "\\")) {
24.1599 + var token = str.substring(tokenStart, position + 1);
24.1600 + tokens.push(token);
24.1601 + var replacement = "@ko_token_" + (tokens.length - 1) + "@";
24.1602 + str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
24.1603 + position -= (token.length - replacement.length);
24.1604 + tokenStart = null;
24.1605 + }
24.1606 + }
24.1607 +
24.1608 + // Next pull out balanced paren, brace, and bracket blocks
24.1609 + tokenStart = null;
24.1610 + tokenEndChar = null;
24.1611 + var tokenDepth = 0, tokenStartChar = null;
24.1612 + for (var position = 0; position < str.length; position++) {
24.1613 + var c = str.charAt(position);
24.1614 + if (tokenStart === null) {
24.1615 + switch (c) {
24.1616 + case "{": tokenStart = position; tokenStartChar = c;
24.1617 + tokenEndChar = "}";
24.1618 + break;
24.1619 + case "(": tokenStart = position; tokenStartChar = c;
24.1620 + tokenEndChar = ")";
24.1621 + break;
24.1622 + case "[": tokenStart = position; tokenStartChar = c;
24.1623 + tokenEndChar = "]";
24.1624 + break;
24.1625 + }
24.1626 + }
24.1627 +
24.1628 + if (c === tokenStartChar)
24.1629 + tokenDepth++;
24.1630 + else if (c === tokenEndChar) {
24.1631 + tokenDepth--;
24.1632 + if (tokenDepth === 0) {
24.1633 + var token = str.substring(tokenStart, position + 1);
24.1634 + tokens.push(token);
24.1635 + var replacement = "@ko_token_" + (tokens.length - 1) + "@";
24.1636 + str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
24.1637 + position -= (token.length - replacement.length);
24.1638 + tokenStart = null;
24.1639 + }
24.1640 + }
24.1641 + }
24.1642 +
24.1643 + // Now we can safely split on commas to get the key/value pairs
24.1644 + var result = [];
24.1645 + var keyValuePairs = str.split(",");
24.1646 + for (var i = 0, j = keyValuePairs.length; i < j; i++) {
24.1647 + var pair = keyValuePairs[i];
24.1648 + var colonPos = pair.indexOf(":");
24.1649 + if ((colonPos > 0) && (colonPos < pair.length - 1)) {
24.1650 + var key = pair.substring(0, colonPos);
24.1651 + var value = pair.substring(colonPos + 1);
24.1652 + result.push({ 'key': restoreTokens(key, tokens), 'value': restoreTokens(value, tokens) });
24.1653 + } else {
24.1654 + result.push({ 'unknown': restoreTokens(pair, tokens) });
24.1655 + }
24.1656 + }
24.1657 + return result;
24.1658 + },
24.1659 +
24.1660 + preProcessBindings: function (objectLiteralStringOrKeyValueArray) {
24.1661 + var keyValueArray = typeof objectLiteralStringOrKeyValueArray === "string"
24.1662 + ? ko.expressionRewriting.parseObjectLiteral(objectLiteralStringOrKeyValueArray)
24.1663 + : objectLiteralStringOrKeyValueArray;
24.1664 + var resultStrings = [], propertyAccessorResultStrings = [];
24.1665 +
24.1666 + var keyValueEntry;
24.1667 + for (var i = 0; keyValueEntry = keyValueArray[i]; i++) {
24.1668 + if (resultStrings.length > 0)
24.1669 + resultStrings.push(",");
24.1670 +
24.1671 + if (keyValueEntry['key']) {
24.1672 + var quotedKey = ensureQuoted(keyValueEntry['key']), val = keyValueEntry['value'];
24.1673 + resultStrings.push(quotedKey);
24.1674 + resultStrings.push(":");
24.1675 + resultStrings.push(val);
24.1676 +
24.1677 + if (val = getWriteableValue(ko.utils.stringTrim(val))) {
24.1678 + if (propertyAccessorResultStrings.length > 0)
24.1679 + propertyAccessorResultStrings.push(", ");
24.1680 + propertyAccessorResultStrings.push(quotedKey + " : function(__ko_value) { " + val + " = __ko_value; }");
24.1681 + }
24.1682 + } else if (keyValueEntry['unknown']) {
24.1683 + resultStrings.push(keyValueEntry['unknown']);
24.1684 + }
24.1685 + }
24.1686 +
24.1687 + var combinedResult = resultStrings.join("");
24.1688 + if (propertyAccessorResultStrings.length > 0) {
24.1689 + var allPropertyAccessors = propertyAccessorResultStrings.join("");
24.1690 + combinedResult = combinedResult + ", '_ko_property_writers' : { " + allPropertyAccessors + " } ";
24.1691 + }
24.1692 +
24.1693 + return combinedResult;
24.1694 + },
24.1695 +
24.1696 + keyValueArrayContainsKey: function(keyValueArray, key) {
24.1697 + for (var i = 0; i < keyValueArray.length; i++)
24.1698 + if (ko.utils.stringTrim(keyValueArray[i]['key']) == key)
24.1699 + return true;
24.1700 + return false;
24.1701 + },
24.1702 +
24.1703 + // Internal, private KO utility for updating model properties from within bindings
24.1704 + // property: If the property being updated is (or might be) an observable, pass it here
24.1705 + // If it turns out to be a writable observable, it will be written to directly
24.1706 + // allBindingsAccessor: All bindings in the current execution context.
24.1707 + // This will be searched for a '_ko_property_writers' property in case you're writing to a non-observable
24.1708 + // key: The key identifying the property to be written. Example: for { hasFocus: myValue }, write to 'myValue' by specifying the key 'hasFocus'
24.1709 + // value: The value to be written
24.1710 + // checkIfDifferent: If true, and if the property being written is a writable observable, the value will only be written if
24.1711 + // it is !== existing value on that writable observable
24.1712 + writeValueToProperty: function(property, allBindingsAccessor, key, value, checkIfDifferent) {
24.1713 + if (!property || !ko.isWriteableObservable(property)) {
24.1714 + var propWriters = allBindingsAccessor()['_ko_property_writers'];
24.1715 + if (propWriters && propWriters[key])
24.1716 + propWriters[key](value);
24.1717 + } else if (!checkIfDifferent || property.peek() !== value) {
24.1718 + property(value);
24.1719 + }
24.1720 + }
24.1721 + };
24.1722 +})();
24.1723 +
24.1724 +ko.exportSymbol('expressionRewriting', ko.expressionRewriting);
24.1725 +ko.exportSymbol('expressionRewriting.bindingRewriteValidators', ko.expressionRewriting.bindingRewriteValidators);
24.1726 +ko.exportSymbol('expressionRewriting.parseObjectLiteral', ko.expressionRewriting.parseObjectLiteral);
24.1727 +ko.exportSymbol('expressionRewriting.preProcessBindings', ko.expressionRewriting.preProcessBindings);
24.1728 +
24.1729 +// For backward compatibility, define the following aliases. (Previously, these function names were misleading because
24.1730 +// they referred to JSON specifically, even though they actually work with arbitrary JavaScript object literal expressions.)
24.1731 +ko.exportSymbol('jsonExpressionRewriting', ko.expressionRewriting);
24.1732 +ko.exportSymbol('jsonExpressionRewriting.insertPropertyAccessorsIntoJson', ko.expressionRewriting.preProcessBindings);(function() {
24.1733 + // "Virtual elements" is an abstraction on top of the usual DOM API which understands the notion that comment nodes
24.1734 + // may be used to represent hierarchy (in addition to the DOM's natural hierarchy).
24.1735 + // If you call the DOM-manipulating functions on ko.virtualElements, you will be able to read and write the state
24.1736 + // of that virtual hierarchy
24.1737 + //
24.1738 + // The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
24.1739 + // without having to scatter special cases all over the binding and templating code.
24.1740 +
24.1741 + // IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
24.1742 + // but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
24.1743 + // So, use node.text where available, and node.nodeValue elsewhere
24.1744 + var commentNodesHaveTextProperty = document.createComment("test").text === "<!--test-->";
24.1745 +
24.1746 + var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*-->$/ : /^\s*ko(?:\s+(.+\s*\:[\s\S]*))?\s*$/;
24.1747 + var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
24.1748 + var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };
24.1749 +
24.1750 + function isStartComment(node) {
24.1751 + return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
24.1752 + }
24.1753 +
24.1754 + function isEndComment(node) {
24.1755 + return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(endCommentRegex);
24.1756 + }
24.1757 +
24.1758 + function getVirtualChildren(startComment, allowUnbalanced) {
24.1759 + var currentNode = startComment;
24.1760 + var depth = 1;
24.1761 + var children = [];
24.1762 + while (currentNode = currentNode.nextSibling) {
24.1763 + if (isEndComment(currentNode)) {
24.1764 + depth--;
24.1765 + if (depth === 0)
24.1766 + return children;
24.1767 + }
24.1768 +
24.1769 + children.push(currentNode);
24.1770 +
24.1771 + if (isStartComment(currentNode))
24.1772 + depth++;
24.1773 + }
24.1774 + if (!allowUnbalanced)
24.1775 + throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
24.1776 + return null;
24.1777 + }
24.1778 +
24.1779 + function getMatchingEndComment(startComment, allowUnbalanced) {
24.1780 + var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
24.1781 + if (allVirtualChildren) {
24.1782 + if (allVirtualChildren.length > 0)
24.1783 + return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
24.1784 + return startComment.nextSibling;
24.1785 + } else
24.1786 + return null; // Must have no matching end comment, and allowUnbalanced is true
24.1787 + }
24.1788 +
24.1789 + function getUnbalancedChildTags(node) {
24.1790 + // e.g., from <div>OK</div><!-- ko blah --><span>Another</span>, returns: <!-- ko blah --><span>Another</span>
24.1791 + // from <div>OK</div><!-- /ko --><!-- /ko -->, returns: <!-- /ko --><!-- /ko -->
24.1792 + var childNode = node.firstChild, captureRemaining = null;
24.1793 + if (childNode) {
24.1794 + do {
24.1795 + if (captureRemaining) // We already hit an unbalanced node and are now just scooping up all subsequent nodes
24.1796 + captureRemaining.push(childNode);
24.1797 + else if (isStartComment(childNode)) {
24.1798 + var matchingEndComment = getMatchingEndComment(childNode, /* allowUnbalanced: */ true);
24.1799 + if (matchingEndComment) // It's a balanced tag, so skip immediately to the end of this virtual set
24.1800 + childNode = matchingEndComment;
24.1801 + else
24.1802 + captureRemaining = [childNode]; // It's unbalanced, so start capturing from this point
24.1803 + } else if (isEndComment(childNode)) {
24.1804 + captureRemaining = [childNode]; // It's unbalanced (if it wasn't, we'd have skipped over it already), so start capturing
24.1805 + }
24.1806 + } while (childNode = childNode.nextSibling);
24.1807 + }
24.1808 + return captureRemaining;
24.1809 + }
24.1810 +
24.1811 + ko.virtualElements = {
24.1812 + allowedBindings: {},
24.1813 +
24.1814 + childNodes: function(node) {
24.1815 + return isStartComment(node) ? getVirtualChildren(node) : node.childNodes;
24.1816 + },
24.1817 +
24.1818 + emptyNode: function(node) {
24.1819 + if (!isStartComment(node))
24.1820 + ko.utils.emptyDomNode(node);
24.1821 + else {
24.1822 + var virtualChildren = ko.virtualElements.childNodes(node);
24.1823 + for (var i = 0, j = virtualChildren.length; i < j; i++)
24.1824 + ko.removeNode(virtualChildren[i]);
24.1825 + }
24.1826 + },
24.1827 +
24.1828 + setDomNodeChildren: function(node, childNodes) {
24.1829 + if (!isStartComment(node))
24.1830 + ko.utils.setDomNodeChildren(node, childNodes);
24.1831 + else {
24.1832 + ko.virtualElements.emptyNode(node);
24.1833 + var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
24.1834 + for (var i = 0, j = childNodes.length; i < j; i++)
24.1835 + endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
24.1836 + }
24.1837 + },
24.1838 +
24.1839 + prepend: function(containerNode, nodeToPrepend) {
24.1840 + if (!isStartComment(containerNode)) {
24.1841 + if (containerNode.firstChild)
24.1842 + containerNode.insertBefore(nodeToPrepend, containerNode.firstChild);
24.1843 + else
24.1844 + containerNode.appendChild(nodeToPrepend);
24.1845 + } else {
24.1846 + // Start comments must always have a parent and at least one following sibling (the end comment)
24.1847 + containerNode.parentNode.insertBefore(nodeToPrepend, containerNode.nextSibling);
24.1848 + }
24.1849 + },
24.1850 +
24.1851 + insertAfter: function(containerNode, nodeToInsert, insertAfterNode) {
24.1852 + if (!insertAfterNode) {
24.1853 + ko.virtualElements.prepend(containerNode, nodeToInsert);
24.1854 + } else if (!isStartComment(containerNode)) {
24.1855 + // Insert after insertion point
24.1856 + if (insertAfterNode.nextSibling)
24.1857 + containerNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
24.1858 + else
24.1859 + containerNode.appendChild(nodeToInsert);
24.1860 + } else {
24.1861 + // Children of start comments must always have a parent and at least one following sibling (the end comment)
24.1862 + containerNode.parentNode.insertBefore(nodeToInsert, insertAfterNode.nextSibling);
24.1863 + }
24.1864 + },
24.1865 +
24.1866 + firstChild: function(node) {
24.1867 + if (!isStartComment(node))
24.1868 + return node.firstChild;
24.1869 + if (!node.nextSibling || isEndComment(node.nextSibling))
24.1870 + return null;
24.1871 + return node.nextSibling;
24.1872 + },
24.1873 +
24.1874 + nextSibling: function(node) {
24.1875 + if (isStartComment(node))
24.1876 + node = getMatchingEndComment(node);
24.1877 + if (node.nextSibling && isEndComment(node.nextSibling))
24.1878 + return null;
24.1879 + return node.nextSibling;
24.1880 + },
24.1881 +
24.1882 + virtualNodeBindingValue: function(node) {
24.1883 + var regexMatch = isStartComment(node);
24.1884 + return regexMatch ? regexMatch[1] : null;
24.1885 + },
24.1886 +
24.1887 + normaliseVirtualElementDomStructure: function(elementVerified) {
24.1888 + // Workaround for https://github.com/SteveSanderson/knockout/issues/155
24.1889 + // (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
24.1890 + // that are direct descendants of <ul> into the preceding <li>)
24.1891 + if (!htmlTagsWithOptionallyClosingChildren[ko.utils.tagNameLower(elementVerified)])
24.1892 + return;
24.1893 +
24.1894 + // Scan immediate children to see if they contain unbalanced comment tags. If they do, those comment tags
24.1895 + // must be intended to appear *after* that child, so move them there.
24.1896 + var childNode = elementVerified.firstChild;
24.1897 + if (childNode) {
24.1898 + do {
24.1899 + if (childNode.nodeType === 1) {
24.1900 + var unbalancedTags = getUnbalancedChildTags(childNode);
24.1901 + if (unbalancedTags) {
24.1902 + // Fix up the DOM by moving the unbalanced tags to where they most likely were intended to be placed - *after* the child
24.1903 + var nodeToInsertBefore = childNode.nextSibling;
24.1904 + for (var i = 0; i < unbalancedTags.length; i++) {
24.1905 + if (nodeToInsertBefore)
24.1906 + elementVerified.insertBefore(unbalancedTags[i], nodeToInsertBefore);
24.1907 + else
24.1908 + elementVerified.appendChild(unbalancedTags[i]);
24.1909 + }
24.1910 + }
24.1911 + }
24.1912 + } while (childNode = childNode.nextSibling);
24.1913 + }
24.1914 + }
24.1915 + };
24.1916 +})();
24.1917 +ko.exportSymbol('virtualElements', ko.virtualElements);
24.1918 +ko.exportSymbol('virtualElements.allowedBindings', ko.virtualElements.allowedBindings);
24.1919 +ko.exportSymbol('virtualElements.emptyNode', ko.virtualElements.emptyNode);
24.1920 +//ko.exportSymbol('virtualElements.firstChild', ko.virtualElements.firstChild); // firstChild is not minified
24.1921 +ko.exportSymbol('virtualElements.insertAfter', ko.virtualElements.insertAfter);
24.1922 +//ko.exportSymbol('virtualElements.nextSibling', ko.virtualElements.nextSibling); // nextSibling is not minified
24.1923 +ko.exportSymbol('virtualElements.prepend', ko.virtualElements.prepend);
24.1924 +ko.exportSymbol('virtualElements.setDomNodeChildren', ko.virtualElements.setDomNodeChildren);
24.1925 +(function() {
24.1926 + var defaultBindingAttributeName = "data-bind";
24.1927 +
24.1928 + ko.bindingProvider = function() {
24.1929 + this.bindingCache = {};
24.1930 + };
24.1931 +
24.1932 + ko.utils.extend(ko.bindingProvider.prototype, {
24.1933 + 'nodeHasBindings': function(node) {
24.1934 + switch (node.nodeType) {
24.1935 + case 1: return node.getAttribute(defaultBindingAttributeName) != null; // Element
24.1936 + case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node
24.1937 + default: return false;
24.1938 + }
24.1939 + },
24.1940 +
24.1941 + 'getBindings': function(node, bindingContext) {
24.1942 + var bindingsString = this['getBindingsString'](node, bindingContext);
24.1943 + return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
24.1944 + },
24.1945 +
24.1946 + // The following function is only used internally by this default provider.
24.1947 + // It's not part of the interface definition for a general binding provider.
24.1948 + 'getBindingsString': function(node, bindingContext) {
24.1949 + switch (node.nodeType) {
24.1950 + case 1: return node.getAttribute(defaultBindingAttributeName); // Element
24.1951 + case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
24.1952 + default: return null;
24.1953 + }
24.1954 + },
24.1955 +
24.1956 + // The following function is only used internally by this default provider.
24.1957 + // It's not part of the interface definition for a general binding provider.
24.1958 + 'parseBindingsString': function(bindingsString, bindingContext, node) {
24.1959 + try {
24.1960 + var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache);
24.1961 + return bindingFunction(bindingContext, node);
24.1962 + } catch (ex) {
24.1963 + throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
24.1964 + }
24.1965 + }
24.1966 + });
24.1967 +
24.1968 + ko.bindingProvider['instance'] = new ko.bindingProvider();
24.1969 +
24.1970 + function createBindingsStringEvaluatorViaCache(bindingsString, cache) {
24.1971 + var cacheKey = bindingsString;
24.1972 + return cache[cacheKey]
24.1973 + || (cache[cacheKey] = createBindingsStringEvaluator(bindingsString));
24.1974 + }
24.1975 +
24.1976 + function createBindingsStringEvaluator(bindingsString) {
24.1977 + // Build the source for a function that evaluates "expression"
24.1978 + // For each scope variable, add an extra level of "with" nesting
24.1979 + // Example result: with(sc1) { with(sc0) { return (expression) } }
24.1980 + var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString),
24.1981 + functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
24.1982 + return new Function("$context", "$element", functionBody);
24.1983 + }
24.1984 +})();
24.1985 +
24.1986 +ko.exportSymbol('bindingProvider', ko.bindingProvider);
24.1987 +(function () {
24.1988 + ko.bindingHandlers = {};
24.1989 +
24.1990 + ko.bindingContext = function(dataItem, parentBindingContext, dataItemAlias) {
24.1991 + if (parentBindingContext) {
24.1992 + ko.utils.extend(this, parentBindingContext); // Inherit $root and any custom properties
24.1993 + this['$parentContext'] = parentBindingContext;
24.1994 + this['$parent'] = parentBindingContext['$data'];
24.1995 + this['$parents'] = (parentBindingContext['$parents'] || []).slice(0);
24.1996 + this['$parents'].unshift(this['$parent']);
24.1997 + } else {
24.1998 + this['$parents'] = [];
24.1999 + this['$root'] = dataItem;
24.2000 + // Export 'ko' in the binding context so it will be available in bindings and templates
24.2001 + // even if 'ko' isn't exported as a global, such as when using an AMD loader.
24.2002 + // See https://github.com/SteveSanderson/knockout/issues/490
24.2003 + this['ko'] = ko;
24.2004 + }
24.2005 + this['$data'] = dataItem;
24.2006 + if (dataItemAlias)
24.2007 + this[dataItemAlias] = dataItem;
24.2008 + }
24.2009 + ko.bindingContext.prototype['createChildContext'] = function (dataItem, dataItemAlias) {
24.2010 + return new ko.bindingContext(dataItem, this, dataItemAlias);
24.2011 + };
24.2012 + ko.bindingContext.prototype['extend'] = function(properties) {
24.2013 + var clone = ko.utils.extend(new ko.bindingContext(), this);
24.2014 + return ko.utils.extend(clone, properties);
24.2015 + };
24.2016 +
24.2017 + function validateThatBindingIsAllowedForVirtualElements(bindingName) {
24.2018 + var validator = ko.virtualElements.allowedBindings[bindingName];
24.2019 + if (!validator)
24.2020 + throw new Error("The binding '" + bindingName + "' cannot be used with virtual elements")
24.2021 + }
24.2022 +
24.2023 + function applyBindingsToDescendantsInternal (viewModel, elementOrVirtualElement, bindingContextsMayDifferFromDomParentElement) {
24.2024 + var currentChild, nextInQueue = ko.virtualElements.firstChild(elementOrVirtualElement);
24.2025 + while (currentChild = nextInQueue) {
24.2026 + // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
24.2027 + nextInQueue = ko.virtualElements.nextSibling(currentChild);
24.2028 + applyBindingsToNodeAndDescendantsInternal(viewModel, currentChild, bindingContextsMayDifferFromDomParentElement);
24.2029 + }
24.2030 + }
24.2031 +
24.2032 + function applyBindingsToNodeAndDescendantsInternal (viewModel, nodeVerified, bindingContextMayDifferFromDomParentElement) {
24.2033 + var shouldBindDescendants = true;
24.2034 +
24.2035 + // Perf optimisation: Apply bindings only if...
24.2036 + // (1) We need to store the binding context on this node (because it may differ from the DOM parent node's binding context)
24.2037 + // Note that we can't store binding contexts on non-elements (e.g., text nodes), as IE doesn't allow expando properties for those
24.2038 + // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
24.2039 + var isElement = (nodeVerified.nodeType === 1);
24.2040 + if (isElement) // Workaround IE <= 8 HTML parsing weirdness
24.2041 + ko.virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
24.2042 +
24.2043 + var shouldApplyBindings = (isElement && bindingContextMayDifferFromDomParentElement) // Case (1)
24.2044 + || ko.bindingProvider['instance']['nodeHasBindings'](nodeVerified); // Case (2)
24.2045 + if (shouldApplyBindings)
24.2046 + shouldBindDescendants = applyBindingsToNodeInternal(nodeVerified, null, viewModel, bindingContextMayDifferFromDomParentElement).shouldBindDescendants;
24.2047 +
24.2048 + if (shouldBindDescendants) {
24.2049 + // We're recursing automatically into (real or virtual) child nodes without changing binding contexts. So,
24.2050 + // * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
24.2051 + // hence bindingContextsMayDifferFromDomParentElement is false
24.2052 + // * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
24.2053 + // skip over any number of intermediate virtual elements, any of which might define a custom binding context,
24.2054 + // hence bindingContextsMayDifferFromDomParentElement is true
24.2055 + applyBindingsToDescendantsInternal(viewModel, nodeVerified, /* bindingContextsMayDifferFromDomParentElement: */ !isElement);
24.2056 + }
24.2057 + }
24.2058 +
24.2059 + function applyBindingsToNodeInternal (node, bindings, viewModelOrBindingContext, bindingContextMayDifferFromDomParentElement) {
24.2060 + // Need to be sure that inits are only run once, and updates never run until all the inits have been run
24.2061 + var initPhase = 0; // 0 = before all inits, 1 = during inits, 2 = after all inits
24.2062 +
24.2063 + // Each time the dependentObservable is evaluated (after data changes),
24.2064 + // the binding attribute is reparsed so that it can pick out the correct
24.2065 + // model properties in the context of the changed data.
24.2066 + // DOM event callbacks need to be able to access this changed data,
24.2067 + // so we need a single parsedBindings variable (shared by all callbacks
24.2068 + // associated with this node's bindings) that all the closures can access.
24.2069 + var parsedBindings;
24.2070 + function makeValueAccessor(bindingKey) {
24.2071 + return function () { return parsedBindings[bindingKey] }
24.2072 + }
24.2073 + function parsedBindingsAccessor() {
24.2074 + return parsedBindings;
24.2075 + }
24.2076 +
24.2077 + var bindingHandlerThatControlsDescendantBindings;
24.2078 + ko.dependentObservable(
24.2079 + function () {
24.2080 + // Ensure we have a nonnull binding context to work with
24.2081 + var bindingContextInstance = viewModelOrBindingContext && (viewModelOrBindingContext instanceof ko.bindingContext)
24.2082 + ? viewModelOrBindingContext
24.2083 + : new ko.bindingContext(ko.utils.unwrapObservable(viewModelOrBindingContext));
24.2084 + var viewModel = bindingContextInstance['$data'];
24.2085 +
24.2086 + // Optimization: Don't store the binding context on this node if it's definitely the same as on node.parentNode, because
24.2087 + // we can easily recover it just by scanning up the node's ancestors in the DOM
24.2088 + // (note: here, parent node means "real DOM parent" not "virtual parent", as there's no O(1) way to find the virtual parent)
24.2089 + if (bindingContextMayDifferFromDomParentElement)
24.2090 + ko.storedBindingContextForNode(node, bindingContextInstance);
24.2091 +
24.2092 + // Use evaluatedBindings if given, otherwise fall back on asking the bindings provider to give us some bindings
24.2093 + var evaluatedBindings = (typeof bindings == "function") ? bindings(bindingContextInstance, node) : bindings;
24.2094 + parsedBindings = evaluatedBindings || ko.bindingProvider['instance']['getBindings'](node, bindingContextInstance);
24.2095 +
24.2096 + if (parsedBindings) {
24.2097 + // First run all the inits, so bindings can register for notification on changes
24.2098 + if (initPhase === 0) {
24.2099 + initPhase = 1;
24.2100 + for (var bindingKey in parsedBindings) {
24.2101 + var binding = ko.bindingHandlers[bindingKey];
24.2102 + if (binding && node.nodeType === 8)
24.2103 + validateThatBindingIsAllowedForVirtualElements(bindingKey);
24.2104 +
24.2105 + if (binding && typeof binding["init"] == "function") {
24.2106 + var handlerInitFn = binding["init"];
24.2107 + var initResult = handlerInitFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
24.2108 +
24.2109 + // If this binding handler claims to control descendant bindings, make a note of this
24.2110 + if (initResult && initResult['controlsDescendantBindings']) {
24.2111 + if (bindingHandlerThatControlsDescendantBindings !== undefined)
24.2112 + 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.");
24.2113 + bindingHandlerThatControlsDescendantBindings = bindingKey;
24.2114 + }
24.2115 + }
24.2116 + }
24.2117 + initPhase = 2;
24.2118 + }
24.2119 +
24.2120 + // ... then run all the updates, which might trigger changes even on the first evaluation
24.2121 + if (initPhase === 2) {
24.2122 + for (var bindingKey in parsedBindings) {
24.2123 + var binding = ko.bindingHandlers[bindingKey];
24.2124 + if (binding && typeof binding["update"] == "function") {
24.2125 + var handlerUpdateFn = binding["update"];
24.2126 + handlerUpdateFn(node, makeValueAccessor(bindingKey), parsedBindingsAccessor, viewModel, bindingContextInstance);
24.2127 + }
24.2128 + }
24.2129 + }
24.2130 + }
24.2131 + },
24.2132 + null,
24.2133 + { disposeWhenNodeIsRemoved : node }
24.2134 + );
24.2135 +
24.2136 + return {
24.2137 + shouldBindDescendants: bindingHandlerThatControlsDescendantBindings === undefined
24.2138 + };
24.2139 + };
24.2140 +
24.2141 + var storedBindingContextDomDataKey = "__ko_bindingContext__";
24.2142 + ko.storedBindingContextForNode = function (node, bindingContext) {
24.2143 + if (arguments.length == 2)
24.2144 + ko.utils.domData.set(node, storedBindingContextDomDataKey, bindingContext);
24.2145 + else
24.2146 + return ko.utils.domData.get(node, storedBindingContextDomDataKey);
24.2147 + }
24.2148 +
24.2149 + ko.applyBindingsToNode = function (node, bindings, viewModel) {
24.2150 + if (node.nodeType === 1) // If it's an element, workaround IE <= 8 HTML parsing weirdness
24.2151 + ko.virtualElements.normaliseVirtualElementDomStructure(node);
24.2152 + return applyBindingsToNodeInternal(node, bindings, viewModel, true);
24.2153 + };
24.2154 +
24.2155 + ko.applyBindingsToDescendants = function(viewModel, rootNode) {
24.2156 + if (rootNode.nodeType === 1 || rootNode.nodeType === 8)
24.2157 + applyBindingsToDescendantsInternal(viewModel, rootNode, true);
24.2158 + };
24.2159 +
24.2160 + ko.applyBindings = function (viewModel, rootNode) {
24.2161 + if (rootNode && (rootNode.nodeType !== 1) && (rootNode.nodeType !== 8))
24.2162 + throw new Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");
24.2163 + rootNode = rootNode || window.document.body; // Make "rootNode" parameter optional
24.2164 +
24.2165 + applyBindingsToNodeAndDescendantsInternal(viewModel, rootNode, true);
24.2166 + };
24.2167 +
24.2168 + // Retrieving binding context from arbitrary nodes
24.2169 + ko.contextFor = function(node) {
24.2170 + // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
24.2171 + switch (node.nodeType) {
24.2172 + case 1:
24.2173 + case 8:
24.2174 + var context = ko.storedBindingContextForNode(node);
24.2175 + if (context) return context;
24.2176 + if (node.parentNode) return ko.contextFor(node.parentNode);
24.2177 + break;
24.2178 + }
24.2179 + return undefined;
24.2180 + };
24.2181 + ko.dataFor = function(node) {
24.2182 + var context = ko.contextFor(node);
24.2183 + return context ? context['$data'] : undefined;
24.2184 + };
24.2185 +
24.2186 + ko.exportSymbol('bindingHandlers', ko.bindingHandlers);
24.2187 + ko.exportSymbol('applyBindings', ko.applyBindings);
24.2188 + ko.exportSymbol('applyBindingsToDescendants', ko.applyBindingsToDescendants);
24.2189 + ko.exportSymbol('applyBindingsToNode', ko.applyBindingsToNode);
24.2190 + ko.exportSymbol('contextFor', ko.contextFor);
24.2191 + ko.exportSymbol('dataFor', ko.dataFor);
24.2192 +})();
24.2193 +var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
24.2194 +ko.bindingHandlers['attr'] = {
24.2195 + 'update': function(element, valueAccessor, allBindingsAccessor) {
24.2196 + var value = ko.utils.unwrapObservable(valueAccessor()) || {};
24.2197 + for (var attrName in value) {
24.2198 + if (typeof attrName == "string") {
24.2199 + var attrValue = ko.utils.unwrapObservable(value[attrName]);
24.2200 +
24.2201 + // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
24.2202 + // when someProp is a "no value"-like value (strictly null, false, or undefined)
24.2203 + // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
24.2204 + var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
24.2205 + if (toRemove)
24.2206 + element.removeAttribute(attrName);
24.2207 +
24.2208 + // In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
24.2209 + // HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
24.2210 + // but instead of figuring out the mode, we'll just set the attribute through the Javascript
24.2211 + // property for IE <= 8.
24.2212 + if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
24.2213 + attrName = attrHtmlToJavascriptMap[attrName];
24.2214 + if (toRemove)
24.2215 + element.removeAttribute(attrName);
24.2216 + else
24.2217 + element[attrName] = attrValue;
24.2218 + } else if (!toRemove) {
24.2219 + try {
24.2220 + element.setAttribute(attrName, attrValue.toString());
24.2221 + } catch (err) {
24.2222 + // ignore for now
24.2223 + if (console) {
24.2224 + console.log("Can't set attribute " + attrName + " to " + attrValue + " error: " + err);
24.2225 + }
24.2226 + }
24.2227 + }
24.2228 +
24.2229 + // Treat "name" specially - although you can think of it as an attribute, it also needs
24.2230 + // special handling on older versions of IE (https://github.com/SteveSanderson/knockout/pull/333)
24.2231 + // Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
24.2232 + // entirely, and there's no strong reason to allow for such casing in HTML.
24.2233 + if (attrName === "name") {
24.2234 + ko.utils.setElementName(element, toRemove ? "" : attrValue.toString());
24.2235 + }
24.2236 + }
24.2237 + }
24.2238 + }
24.2239 +};
24.2240 +ko.bindingHandlers['checked'] = {
24.2241 + 'init': function (element, valueAccessor, allBindingsAccessor) {
24.2242 + var updateHandler = function() {
24.2243 + var valueToWrite;
24.2244 + if (element.type == "checkbox") {
24.2245 + valueToWrite = element.checked;
24.2246 + } else if ((element.type == "radio") && (element.checked)) {
24.2247 + valueToWrite = element.value;
24.2248 + } else {
24.2249 + return; // "checked" binding only responds to checkboxes and selected radio buttons
24.2250 + }
24.2251 +
24.2252 + var modelValue = valueAccessor(), unwrappedValue = ko.utils.unwrapObservable(modelValue);
24.2253 + if ((element.type == "checkbox") && (unwrappedValue instanceof Array)) {
24.2254 + // For checkboxes bound to an array, we add/remove the checkbox value to that array
24.2255 + // This works for both observable and non-observable arrays
24.2256 + var existingEntryIndex = ko.utils.arrayIndexOf(unwrappedValue, element.value);
24.2257 + if (element.checked && (existingEntryIndex < 0))
24.2258 + modelValue.push(element.value);
24.2259 + else if ((!element.checked) && (existingEntryIndex >= 0))
24.2260 + modelValue.splice(existingEntryIndex, 1);
24.2261 + } else {
24.2262 + ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
24.2263 + }
24.2264 + };
24.2265 + ko.utils.registerEventHandler(element, "click", updateHandler);
24.2266 +
24.2267 + // IE 6 won't allow radio buttons to be selected unless they have a name
24.2268 + if ((element.type == "radio") && !element.name)
24.2269 + ko.bindingHandlers['uniqueName']['init'](element, function() { return true });
24.2270 + },
24.2271 + 'update': function (element, valueAccessor) {
24.2272 + var value = ko.utils.unwrapObservable(valueAccessor());
24.2273 +
24.2274 + if (element.type == "checkbox") {
24.2275 + if (value instanceof Array) {
24.2276 + // When bound to an array, the checkbox being checked represents its value being present in that array
24.2277 + element.checked = ko.utils.arrayIndexOf(value, element.value) >= 0;
24.2278 + } else {
24.2279 + // When bound to anything other value (not an array), the checkbox being checked represents the value being trueish
24.2280 + element.checked = value;
24.2281 + }
24.2282 + } else if (element.type == "radio") {
24.2283 + element.checked = (element.value == value);
24.2284 + }
24.2285 + }
24.2286 +};
24.2287 +var classesWrittenByBindingKey = '__ko__cssValue';
24.2288 +ko.bindingHandlers['css'] = {
24.2289 + 'update': function (element, valueAccessor) {
24.2290 + var value = ko.utils.unwrapObservable(valueAccessor());
24.2291 + if (typeof value == "object") {
24.2292 + for (var className in value) {
24.2293 + var shouldHaveClass = ko.utils.unwrapObservable(value[className]);
24.2294 + ko.utils.toggleDomNodeCssClass(element, className, shouldHaveClass);
24.2295 + }
24.2296 + } else {
24.2297 + value = String(value || ''); // Make sure we don't try to store or set a non-string value
24.2298 + ko.utils.toggleDomNodeCssClass(element, element[classesWrittenByBindingKey], false);
24.2299 + element[classesWrittenByBindingKey] = value;
24.2300 + ko.utils.toggleDomNodeCssClass(element, value, true);
24.2301 + }
24.2302 + }
24.2303 +};
24.2304 +ko.bindingHandlers['enable'] = {
24.2305 + 'update': function (element, valueAccessor) {
24.2306 + var value = ko.utils.unwrapObservable(valueAccessor());
24.2307 + if (value && element.disabled)
24.2308 + element.removeAttribute("disabled");
24.2309 + else if ((!value) && (!element.disabled))
24.2310 + element.disabled = true;
24.2311 + }
24.2312 +};
24.2313 +
24.2314 +ko.bindingHandlers['disable'] = {
24.2315 + 'update': function (element, valueAccessor) {
24.2316 + ko.bindingHandlers['enable']['update'](element, function() { return !ko.utils.unwrapObservable(valueAccessor()) });
24.2317 + }
24.2318 +};
24.2319 +// For certain common events (currently just 'click'), allow a simplified data-binding syntax
24.2320 +// e.g. click:handler instead of the usual full-length event:{click:handler}
24.2321 +function makeEventHandlerShortcut(eventName) {
24.2322 + ko.bindingHandlers[eventName] = {
24.2323 + 'init': function(element, valueAccessor, allBindingsAccessor, viewModel) {
24.2324 + var newValueAccessor = function () {
24.2325 + var result = {};
24.2326 + result[eventName] = valueAccessor();
24.2327 + return result;
24.2328 + };
24.2329 + return ko.bindingHandlers['event']['init'].call(this, element, newValueAccessor, allBindingsAccessor, viewModel);
24.2330 + }
24.2331 + }
24.2332 +}
24.2333 +
24.2334 +ko.bindingHandlers['event'] = {
24.2335 + 'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) {
24.2336 + var eventsToHandle = valueAccessor() || {};
24.2337 + for(var eventNameOutsideClosure in eventsToHandle) {
24.2338 + (function() {
24.2339 + var eventName = eventNameOutsideClosure; // Separate variable to be captured by event handler closure
24.2340 + if (typeof eventName == "string") {
24.2341 + ko.utils.registerEventHandler(element, eventName, function (event) {
24.2342 + var handlerReturnValue;
24.2343 + var handlerFunction = valueAccessor()[eventName];
24.2344 + if (!handlerFunction)
24.2345 + return;
24.2346 + var allBindings = allBindingsAccessor();
24.2347 +
24.2348 + try {
24.2349 + // Take all the event args, and prefix with the viewmodel
24.2350 + var argsForHandler = ko.utils.makeArray(arguments);
24.2351 + argsForHandler.unshift(viewModel);
24.2352 + handlerReturnValue = handlerFunction.apply(viewModel, argsForHandler);
24.2353 + } finally {
24.2354 + if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
24.2355 + if (event.preventDefault)
24.2356 + event.preventDefault();
24.2357 + else
24.2358 + event.returnValue = false;
24.2359 + }
24.2360 + }
24.2361 +
24.2362 + var bubble = allBindings[eventName + 'Bubble'] !== false;
24.2363 + if (!bubble) {
24.2364 + event.cancelBubble = true;
24.2365 + if (event.stopPropagation)
24.2366 + event.stopPropagation();
24.2367 + }
24.2368 + });
24.2369 + }
24.2370 + })();
24.2371 + }
24.2372 + }
24.2373 +};
24.2374 +// "foreach: someExpression" is equivalent to "template: { foreach: someExpression }"
24.2375 +// "foreach: { data: someExpression, afterAdd: myfn }" is equivalent to "template: { foreach: someExpression, afterAdd: myfn }"
24.2376 +ko.bindingHandlers['foreach'] = {
24.2377 + makeTemplateValueAccessor: function(valueAccessor) {
24.2378 + return function() {
24.2379 + var modelValue = valueAccessor(),
24.2380 + unwrappedValue = ko.utils.peekObservable(modelValue); // Unwrap without setting a dependency here
24.2381 +
24.2382 + // If unwrappedValue is the array, pass in the wrapped value on its own
24.2383 + // The value will be unwrapped and tracked within the template binding
24.2384 + // (See https://github.com/SteveSanderson/knockout/issues/523)
24.2385 + if ((!unwrappedValue) || typeof unwrappedValue.length == "number")
24.2386 + return { 'foreach': modelValue, 'templateEngine': ko.nativeTemplateEngine.instance };
24.2387 +
24.2388 + // If unwrappedValue.data is the array, preserve all relevant options and unwrap again value so we get updates
24.2389 + ko.utils.unwrapObservable(modelValue);
24.2390 + return {
24.2391 + 'foreach': unwrappedValue['data'],
24.2392 + 'as': unwrappedValue['as'],
24.2393 + 'includeDestroyed': unwrappedValue['includeDestroyed'],
24.2394 + 'afterAdd': unwrappedValue['afterAdd'],
24.2395 + 'beforeRemove': unwrappedValue['beforeRemove'],
24.2396 + 'afterRender': unwrappedValue['afterRender'],
24.2397 + 'beforeMove': unwrappedValue['beforeMove'],
24.2398 + 'afterMove': unwrappedValue['afterMove'],
24.2399 + 'templateEngine': ko.nativeTemplateEngine.instance
24.2400 + };
24.2401 + };
24.2402 + },
24.2403 + 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
24.2404 + return ko.bindingHandlers['template']['init'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor));
24.2405 + },
24.2406 + 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
24.2407 + return ko.bindingHandlers['template']['update'](element, ko.bindingHandlers['foreach'].makeTemplateValueAccessor(valueAccessor), allBindingsAccessor, viewModel, bindingContext);
24.2408 + }
24.2409 +};
24.2410 +ko.expressionRewriting.bindingRewriteValidators['foreach'] = false; // Can't rewrite control flow bindings
24.2411 +ko.virtualElements.allowedBindings['foreach'] = true;
24.2412 +var hasfocusUpdatingProperty = '__ko_hasfocusUpdating';
24.2413 +ko.bindingHandlers['hasfocus'] = {
24.2414 + 'init': function(element, valueAccessor, allBindingsAccessor) {
24.2415 + var handleElementFocusChange = function(isFocused) {
24.2416 + // Where possible, ignore which event was raised and determine focus state using activeElement,
24.2417 + // as this avoids phantom focus/blur events raised when changing tabs in modern browsers.
24.2418 + // However, not all KO-targeted browsers (Firefox 2) support activeElement. For those browsers,
24.2419 + // prevent a loss of focus when changing tabs/windows by setting a flag that prevents hasfocus
24.2420 + // from calling 'blur()' on the element when it loses focus.
24.2421 + // Discussion at https://github.com/SteveSanderson/knockout/pull/352
24.2422 + element[hasfocusUpdatingProperty] = true;
24.2423 + var ownerDoc = element.ownerDocument;
24.2424 + if ("activeElement" in ownerDoc) {
24.2425 + isFocused = (ownerDoc.activeElement === element);
24.2426 + }
24.2427 + var modelValue = valueAccessor();
24.2428 + ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'hasfocus', isFocused, true);
24.2429 + element[hasfocusUpdatingProperty] = false;
24.2430 + };
24.2431 + var handleElementFocusIn = handleElementFocusChange.bind(null, true);
24.2432 + var handleElementFocusOut = handleElementFocusChange.bind(null, false);
24.2433 +
24.2434 + ko.utils.registerEventHandler(element, "focus", handleElementFocusIn);
24.2435 + ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn); // For IE
24.2436 + ko.utils.registerEventHandler(element, "blur", handleElementFocusOut);
24.2437 + ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut); // For IE
24.2438 + },
24.2439 + 'update': function(element, valueAccessor) {
24.2440 + var value = ko.utils.unwrapObservable(valueAccessor());
24.2441 + if (!element[hasfocusUpdatingProperty]) {
24.2442 + value ? element.focus() : element.blur();
24.2443 + ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, value ? "focusin" : "focusout"]); // For IE, which doesn't reliably fire "focus" or "blur" events synchronously
24.2444 + }
24.2445 + }
24.2446 +};
24.2447 +ko.bindingHandlers['html'] = {
24.2448 + 'init': function() {
24.2449 + // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
24.2450 + return { 'controlsDescendantBindings': true };
24.2451 + },
24.2452 + 'update': function (element, valueAccessor) {
24.2453 + // setHtml will unwrap the value if needed
24.2454 + ko.utils.setHtml(element, valueAccessor());
24.2455 + }
24.2456 +};
24.2457 +var withIfDomDataKey = '__ko_withIfBindingData';
24.2458 +// Makes a binding like with or if
24.2459 +function makeWithIfBinding(bindingKey, isWith, isNot, makeContextCallback) {
24.2460 + ko.bindingHandlers[bindingKey] = {
24.2461 + 'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
24.2462 + ko.utils.domData.set(element, withIfDomDataKey, {});
24.2463 + return { 'controlsDescendantBindings': true };
24.2464 + },
24.2465 + 'update': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
24.2466 + var withIfData = ko.utils.domData.get(element, withIfDomDataKey),
24.2467 + dataValue = ko.utils.unwrapObservable(valueAccessor()),
24.2468 + shouldDisplay = !isNot !== !dataValue, // equivalent to isNot ? !dataValue : !!dataValue
24.2469 + isFirstRender = !withIfData.savedNodes,
24.2470 + needsRefresh = isFirstRender || isWith || (shouldDisplay !== withIfData.didDisplayOnLastUpdate);
24.2471 +
24.2472 + if (needsRefresh) {
24.2473 + if (isFirstRender) {
24.2474 + withIfData.savedNodes = ko.utils.cloneNodes(ko.virtualElements.childNodes(element), true /* shouldCleanNodes */);
24.2475 + }
24.2476 +
24.2477 + if (shouldDisplay) {
24.2478 + if (!isFirstRender) {
24.2479 + ko.virtualElements.setDomNodeChildren(element, ko.utils.cloneNodes(withIfData.savedNodes));
24.2480 + }
24.2481 + ko.applyBindingsToDescendants(makeContextCallback ? makeContextCallback(bindingContext, dataValue) : bindingContext, element);
24.2482 + } else {
24.2483 + ko.virtualElements.emptyNode(element);
24.2484 + }
24.2485 +
24.2486 + withIfData.didDisplayOnLastUpdate = shouldDisplay;
24.2487 + }
24.2488 + }
24.2489 + };
24.2490 + ko.expressionRewriting.bindingRewriteValidators[bindingKey] = false; // Can't rewrite control flow bindings
24.2491 + ko.virtualElements.allowedBindings[bindingKey] = true;
24.2492 +}
24.2493 +
24.2494 +// Construct the actual binding handlers
24.2495 +makeWithIfBinding('if');
24.2496 +makeWithIfBinding('ifnot', false /* isWith */, true /* isNot */);
24.2497 +makeWithIfBinding('with', true /* isWith */, false /* isNot */,
24.2498 + function(bindingContext, dataValue) {
24.2499 + return bindingContext['createChildContext'](dataValue);
24.2500 + }
24.2501 +);
24.2502 +function ensureDropdownSelectionIsConsistentWithModelValue(element, modelValue, preferModelValue) {
24.2503 + if (preferModelValue) {
24.2504 + if (modelValue !== ko.selectExtensions.readValue(element))
24.2505 + ko.selectExtensions.writeValue(element, modelValue);
24.2506 + }
24.2507 +
24.2508 + // No matter which direction we're syncing in, we want the end result to be equality between dropdown value and model value.
24.2509 + // If they aren't equal, either we prefer the dropdown value, or the model value couldn't be represented, so either way,
24.2510 + // change the model value to match the dropdown.
24.2511 + if (modelValue !== ko.selectExtensions.readValue(element))
24.2512 + ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
24.2513 +};
24.2514 +
24.2515 +ko.bindingHandlers['options'] = {
24.2516 + 'update': function (element, valueAccessor, allBindingsAccessor) {
24.2517 + if (ko.utils.tagNameLower(element) !== "select")
24.2518 + throw new Error("options binding applies only to SELECT elements");
24.2519 +
24.2520 + var selectWasPreviouslyEmpty = element.length == 0;
24.2521 + var previousSelectedValues = ko.utils.arrayMap(ko.utils.arrayFilter(element.childNodes, function (node) {
24.2522 + return node.tagName && (ko.utils.tagNameLower(node) === "option") && node.selected;
24.2523 + }), function (node) {
24.2524 + return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
24.2525 + });
24.2526 + var previousScrollTop = element.scrollTop;
24.2527 +
24.2528 + var value = ko.utils.unwrapObservable(valueAccessor());
24.2529 + var selectedValue = element.value;
24.2530 +
24.2531 + // Remove all existing <option>s.
24.2532 + // Need to use .remove() rather than .removeChild() for <option>s otherwise IE behaves oddly (https://github.com/SteveSanderson/knockout/issues/134)
24.2533 + while (element.length > 0) {
24.2534 + ko.cleanNode(element.options[0]);
24.2535 + element.remove(0);
24.2536 + }
24.2537 +
24.2538 + if (value) {
24.2539 + var allBindings = allBindingsAccessor(),
24.2540 + includeDestroyed = allBindings['optionsIncludeDestroyed'];
24.2541 +
24.2542 + if (typeof value.length != "number")
24.2543 + value = [value];
24.2544 + if (allBindings['optionsCaption']) {
24.2545 + var option = document.createElement("option");
24.2546 + ko.utils.setHtml(option, allBindings['optionsCaption']);
24.2547 + ko.selectExtensions.writeValue(option, undefined);
24.2548 + element.appendChild(option);
24.2549 + }
24.2550 +
24.2551 + for (var i = 0, j = value.length; i < j; i++) {
24.2552 + // Skip destroyed items
24.2553 + var arrayEntry = value[i];
24.2554 + if (arrayEntry && arrayEntry['_destroy'] && !includeDestroyed)
24.2555 + continue;
24.2556 +
24.2557 + var option = document.createElement("option");
24.2558 +
24.2559 + function applyToObject(object, predicate, defaultValue) {
24.2560 + var predicateType = typeof predicate;
24.2561 + if (predicateType == "function") // Given a function; run it against the data value
24.2562 + return predicate(object);
24.2563 + else if (predicateType == "string") // Given a string; treat it as a property name on the data value
24.2564 + return object[predicate];
24.2565 + else // Given no optionsText arg; use the data value itself
24.2566 + return defaultValue;
24.2567 + }
24.2568 +
24.2569 + // Apply a value to the option element
24.2570 + var optionValue = applyToObject(arrayEntry, allBindings['optionsValue'], arrayEntry);
24.2571 + ko.selectExtensions.writeValue(option, ko.utils.unwrapObservable(optionValue));
24.2572 +
24.2573 + // Apply some text to the option element
24.2574 + var optionText = applyToObject(arrayEntry, allBindings['optionsText'], optionValue);
24.2575 + ko.utils.setTextContent(option, optionText);
24.2576 +
24.2577 + element.appendChild(option);
24.2578 + }
24.2579 +
24.2580 + // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document.
24.2581 + // That's why we first added them without selection. Now it's time to set the selection.
24.2582 + var newOptions = element.getElementsByTagName("option");
24.2583 + var countSelectionsRetained = 0;
24.2584 + for (var i = 0, j = newOptions.length; i < j; i++) {
24.2585 + if (ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[i])) >= 0) {
24.2586 + ko.utils.setOptionNodeSelectionState(newOptions[i], true);
24.2587 + countSelectionsRetained++;
24.2588 + }
24.2589 + }
24.2590 +
24.2591 + element.scrollTop = previousScrollTop;
24.2592 +
24.2593 + if (selectWasPreviouslyEmpty && ('value' in allBindings)) {
24.2594 + // Ensure consistency between model value and selected option.
24.2595 + // If the dropdown is being populated for the first time here (or was otherwise previously empty),
24.2596 + // the dropdown selection state is meaningless, so we preserve the model value.
24.2597 + ensureDropdownSelectionIsConsistentWithModelValue(element, ko.utils.peekObservable(allBindings['value']), /* preferModelValue */ true);
24.2598 + }
24.2599 +
24.2600 + // Workaround for IE9 bug
24.2601 + ko.utils.ensureSelectElementIsRenderedCorrectly(element);
24.2602 + }
24.2603 + }
24.2604 +};
24.2605 +ko.bindingHandlers['options'].optionValueDomDataKey = '__ko.optionValueDomData__';
24.2606 +ko.bindingHandlers['selectedOptions'] = {
24.2607 + 'init': function (element, valueAccessor, allBindingsAccessor) {
24.2608 + ko.utils.registerEventHandler(element, "change", function () {
24.2609 + var value = valueAccessor(), valueToWrite = [];
24.2610 + ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
24.2611 + if (node.selected)
24.2612 + valueToWrite.push(ko.selectExtensions.readValue(node));
24.2613 + });
24.2614 + ko.expressionRewriting.writeValueToProperty(value, allBindingsAccessor, 'value', valueToWrite);
24.2615 + });
24.2616 + },
24.2617 + 'update': function (element, valueAccessor) {
24.2618 + if (ko.utils.tagNameLower(element) != "select")
24.2619 + throw new Error("values binding applies only to SELECT elements");
24.2620 +
24.2621 + var newValue = ko.utils.unwrapObservable(valueAccessor());
24.2622 + if (newValue && typeof newValue.length == "number") {
24.2623 + ko.utils.arrayForEach(element.getElementsByTagName("option"), function(node) {
24.2624 + var isSelected = ko.utils.arrayIndexOf(newValue, ko.selectExtensions.readValue(node)) >= 0;
24.2625 + ko.utils.setOptionNodeSelectionState(node, isSelected);
24.2626 + });
24.2627 + }
24.2628 + }
24.2629 +};
24.2630 +ko.bindingHandlers['style'] = {
24.2631 + 'update': function (element, valueAccessor) {
24.2632 + var value = ko.utils.unwrapObservable(valueAccessor() || {});
24.2633 + for (var styleName in value) {
24.2634 + if (typeof styleName == "string") {
24.2635 + var styleValue = ko.utils.unwrapObservable(value[styleName]);
24.2636 + element.style[styleName] = styleValue || ""; // Empty string removes the value, whereas null/undefined have no effect
24.2637 + }
24.2638 + }
24.2639 + }
24.2640 +};
24.2641 +ko.bindingHandlers['submit'] = {
24.2642 + 'init': function (element, valueAccessor, allBindingsAccessor, viewModel) {
24.2643 + if (typeof valueAccessor() != "function")
24.2644 + throw new Error("The value for a submit binding must be a function");
24.2645 + ko.utils.registerEventHandler(element, "submit", function (event) {
24.2646 + var handlerReturnValue;
24.2647 + var value = valueAccessor();
24.2648 + try { handlerReturnValue = value.call(viewModel, element); }
24.2649 + finally {
24.2650 + if (handlerReturnValue !== true) { // Normally we want to prevent default action. Developer can override this be explicitly returning true.
24.2651 + if (event.preventDefault)
24.2652 + event.preventDefault();
24.2653 + else
24.2654 + event.returnValue = false;
24.2655 + }
24.2656 + }
24.2657 + });
24.2658 + }
24.2659 +};
24.2660 +ko.bindingHandlers['text'] = {
24.2661 + 'update': function (element, valueAccessor) {
24.2662 + ko.utils.setTextContent(element, valueAccessor());
24.2663 + }
24.2664 +};
24.2665 +ko.virtualElements.allowedBindings['text'] = true;
24.2666 +ko.bindingHandlers['uniqueName'] = {
24.2667 + 'init': function (element, valueAccessor) {
24.2668 + if (valueAccessor()) {
24.2669 + var name = "ko_unique_" + (++ko.bindingHandlers['uniqueName'].currentIndex);
24.2670 + ko.utils.setElementName(element, name);
24.2671 + }
24.2672 + }
24.2673 +};
24.2674 +ko.bindingHandlers['uniqueName'].currentIndex = 0;
24.2675 +ko.bindingHandlers['value'] = {
24.2676 + 'init': function (element, valueAccessor, allBindingsAccessor) {
24.2677 + // Always catch "change" event; possibly other events too if asked
24.2678 + var eventsToCatch = ["change"];
24.2679 + var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
24.2680 + var propertyChangedFired = false;
24.2681 + if (requestedEventsToCatch) {
24.2682 + if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
24.2683 + requestedEventsToCatch = [requestedEventsToCatch];
24.2684 + ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
24.2685 + eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
24.2686 + }
24.2687 +
24.2688 + var valueUpdateHandler = function() {
24.2689 + propertyChangedFired = false;
24.2690 + var modelValue = valueAccessor();
24.2691 + var elementValue = ko.selectExtensions.readValue(element);
24.2692 + ko.expressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue);
24.2693 + }
24.2694 +
24.2695 + // Workaround for https://github.com/SteveSanderson/knockout/issues/122
24.2696 + // IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
24.2697 + var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
24.2698 + && element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
24.2699 + if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
24.2700 + ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
24.2701 + ko.utils.registerEventHandler(element, "blur", function() {
24.2702 + if (propertyChangedFired) {
24.2703 + valueUpdateHandler();
24.2704 + }
24.2705 + });
24.2706 + }
24.2707 +
24.2708 + ko.utils.arrayForEach(eventsToCatch, function(eventName) {
24.2709 + // The syntax "after<eventname>" means "run the handler asynchronously after the event"
24.2710 + // This is useful, for example, to catch "keydown" events after the browser has updated the control
24.2711 + // (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
24.2712 + var handler = valueUpdateHandler;
24.2713 + if (ko.utils.stringStartsWith(eventName, "after")) {
24.2714 + handler = function() { setTimeout(valueUpdateHandler, 0) };
24.2715 + eventName = eventName.substring("after".length);
24.2716 + }
24.2717 + ko.utils.registerEventHandler(element, eventName, handler);
24.2718 + });
24.2719 + },
24.2720 + 'update': function (element, valueAccessor) {
24.2721 + var valueIsSelectOption = ko.utils.tagNameLower(element) === "select";
24.2722 + var newValue = ko.utils.unwrapObservable(valueAccessor());
24.2723 + var elementValue = ko.selectExtensions.readValue(element);
24.2724 + var valueHasChanged = (newValue != elementValue);
24.2725 +
24.2726 + // 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).
24.2727 + // 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.
24.2728 + if ((newValue === 0) && (elementValue !== 0) && (elementValue !== "0"))
24.2729 + valueHasChanged = true;
24.2730 +
24.2731 + if (valueHasChanged) {
24.2732 + var applyValueAction = function () { ko.selectExtensions.writeValue(element, newValue); };
24.2733 + applyValueAction();
24.2734 +
24.2735 + // Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
24.2736 + // right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
24.2737 + // to apply the value as well.
24.2738 + var alsoApplyAsynchronously = valueIsSelectOption;
24.2739 + if (alsoApplyAsynchronously)
24.2740 + setTimeout(applyValueAction, 0);
24.2741 + }
24.2742 +
24.2743 + // If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
24.2744 + // because you're not allowed to have a model value that disagrees with a visible UI selection.
24.2745 + if (valueIsSelectOption && (element.length > 0))
24.2746 + ensureDropdownSelectionIsConsistentWithModelValue(element, newValue, /* preferModelValue */ false);
24.2747 + }
24.2748 +};
24.2749 +ko.bindingHandlers['visible'] = {
24.2750 + 'update': function (element, valueAccessor) {
24.2751 + var value = ko.utils.unwrapObservable(valueAccessor());
24.2752 + var isCurrentlyVisible = !(element.style.display == "none");
24.2753 + if (value && !isCurrentlyVisible)
24.2754 + element.style.display = "";
24.2755 + else if ((!value) && isCurrentlyVisible)
24.2756 + element.style.display = "none";
24.2757 + }
24.2758 +};
24.2759 +// 'click' is just a shorthand for the usual full-length event:{click:handler}
24.2760 +makeEventHandlerShortcut('click');
24.2761 +// If you want to make a custom template engine,
24.2762 +//
24.2763 +// [1] Inherit from this class (like ko.nativeTemplateEngine does)
24.2764 +// [2] Override 'renderTemplateSource', supplying a function with this signature:
24.2765 +//
24.2766 +// function (templateSource, bindingContext, options) {
24.2767 +// // - templateSource.text() is the text of the template you should render
24.2768 +// // - bindingContext.$data is the data you should pass into the template
24.2769 +// // - you might also want to make bindingContext.$parent, bindingContext.$parents,
24.2770 +// // and bindingContext.$root available in the template too
24.2771 +// // - options gives you access to any other properties set on "data-bind: { template: options }"
24.2772 +// //
24.2773 +// // Return value: an array of DOM nodes
24.2774 +// }
24.2775 +//
24.2776 +// [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
24.2777 +//
24.2778 +// function (script) {
24.2779 +// // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
24.2780 +// // For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
24.2781 +// }
24.2782 +//
24.2783 +// This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
24.2784 +// If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
24.2785 +// and then you don't need to override 'createJavaScriptEvaluatorBlock'.
24.2786 +
24.2787 +ko.templateEngine = function () { };
24.2788 +
24.2789 +ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
24.2790 + throw new Error("Override renderTemplateSource");
24.2791 +};
24.2792 +
24.2793 +ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
24.2794 + throw new Error("Override createJavaScriptEvaluatorBlock");
24.2795 +};
24.2796 +
24.2797 +ko.templateEngine.prototype['makeTemplateSource'] = function(template, templateDocument) {
24.2798 + // Named template
24.2799 + if (typeof template == "string") {
24.2800 + templateDocument = templateDocument || document;
24.2801 + var elem = templateDocument.getElementById(template);
24.2802 + if (!elem)
24.2803 + throw new Error("Cannot find template with ID " + template);
24.2804 + return new ko.templateSources.domElement(elem);
24.2805 + } else if ((template.nodeType == 1) || (template.nodeType == 8)) {
24.2806 + // Anonymous template
24.2807 + return new ko.templateSources.anonymousTemplate(template);
24.2808 + } else
24.2809 + throw new Error("Unknown template type: " + template);
24.2810 +};
24.2811 +
24.2812 +ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options, templateDocument) {
24.2813 + var templateSource = this['makeTemplateSource'](template, templateDocument);
24.2814 + return this['renderTemplateSource'](templateSource, bindingContext, options);
24.2815 +};
24.2816 +
24.2817 +ko.templateEngine.prototype['isTemplateRewritten'] = function (template, templateDocument) {
24.2818 + // Skip rewriting if requested
24.2819 + if (this['allowTemplateRewriting'] === false)
24.2820 + return true;
24.2821 + return this['makeTemplateSource'](template, templateDocument)['data']("isRewritten");
24.2822 +};
24.2823 +
24.2824 +ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, templateDocument) {
24.2825 + var templateSource = this['makeTemplateSource'](template, templateDocument);
24.2826 + var rewritten = rewriterCallback(templateSource['text']());
24.2827 + templateSource['text'](rewritten);
24.2828 + templateSource['data']("isRewritten", true);
24.2829 +};
24.2830 +
24.2831 +ko.exportSymbol('templateEngine', ko.templateEngine);
24.2832 +
24.2833 +ko.templateRewriting = (function () {
24.2834 + var memoizeDataBindingAttributeSyntaxRegex = /(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9\-]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi;
24.2835 + var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
24.2836 +
24.2837 + function validateDataBindValuesForRewriting(keyValueArray) {
24.2838 + var allValidators = ko.expressionRewriting.bindingRewriteValidators;
24.2839 + for (var i = 0; i < keyValueArray.length; i++) {
24.2840 + var key = keyValueArray[i]['key'];
24.2841 + if (allValidators.hasOwnProperty(key)) {
24.2842 + var validator = allValidators[key];
24.2843 +
24.2844 + if (typeof validator === "function") {
24.2845 + var possibleErrorMessage = validator(keyValueArray[i]['value']);
24.2846 + if (possibleErrorMessage)
24.2847 + throw new Error(possibleErrorMessage);
24.2848 + } else if (!validator) {
24.2849 + throw new Error("This template engine does not support the '" + key + "' binding within its templates");
24.2850 + }
24.2851 + }
24.2852 + }
24.2853 + }
24.2854 +
24.2855 + function constructMemoizedTagReplacement(dataBindAttributeValue, tagToRetain, templateEngine) {
24.2856 + var dataBindKeyValueArray = ko.expressionRewriting.parseObjectLiteral(dataBindAttributeValue);
24.2857 + validateDataBindValuesForRewriting(dataBindKeyValueArray);
24.2858 + var rewrittenDataBindAttributeValue = ko.expressionRewriting.preProcessBindings(dataBindKeyValueArray);
24.2859 +
24.2860 + // For no obvious reason, Opera fails to evaluate rewrittenDataBindAttributeValue unless it's wrapped in an additional
24.2861 + // anonymous function, even though Opera's built-in debugger can evaluate it anyway. No other browser requires this
24.2862 + // extra indirection.
24.2863 + var applyBindingsToNextSiblingScript =
24.2864 + "ko.__tr_ambtns(function($context,$element){return(function(){return{ " + rewrittenDataBindAttributeValue + " } })()})";
24.2865 + return templateEngine['createJavaScriptEvaluatorBlock'](applyBindingsToNextSiblingScript) + tagToRetain;
24.2866 + }
24.2867 +
24.2868 + return {
24.2869 + ensureTemplateIsRewritten: function (template, templateEngine, templateDocument) {
24.2870 + if (!templateEngine['isTemplateRewritten'](template, templateDocument))
24.2871 + templateEngine['rewriteTemplate'](template, function (htmlString) {
24.2872 + return ko.templateRewriting.memoizeBindingAttributeSyntax(htmlString, templateEngine);
24.2873 + }, templateDocument);
24.2874 + },
24.2875 +
24.2876 + memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
24.2877 + return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
24.2878 + return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[6], /* tagToRetain: */ arguments[1], templateEngine);
24.2879 + }).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
24.2880 + return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", templateEngine);
24.2881 + });
24.2882 + },
24.2883 +
24.2884 + applyMemoizedBindingsToNextSibling: function (bindings) {
24.2885 + return ko.memoization.memoize(function (domNode, bindingContext) {
24.2886 + if (domNode.nextSibling)
24.2887 + ko.applyBindingsToNode(domNode.nextSibling, bindings, bindingContext);
24.2888 + });
24.2889 + }
24.2890 + }
24.2891 +})();
24.2892 +
24.2893 +
24.2894 +// Exported only because it has to be referenced by string lookup from within rewritten template
24.2895 +ko.exportSymbol('__tr_ambtns', ko.templateRewriting.applyMemoizedBindingsToNextSibling);
24.2896 +(function() {
24.2897 + // A template source represents a read/write way of accessing a template. This is to eliminate the need for template loading/saving
24.2898 + // logic to be duplicated in every template engine (and means they can all work with anonymous templates, etc.)
24.2899 + //
24.2900 + // Two are provided by default:
24.2901 + // 1. ko.templateSources.domElement - reads/writes the text content of an arbitrary DOM element
24.2902 + // 2. ko.templateSources.anonymousElement - uses ko.utils.domData to read/write text *associated* with the DOM element, but
24.2903 + // without reading/writing the actual element text content, since it will be overwritten
24.2904 + // with the rendered template output.
24.2905 + // You can implement your own template source if you want to fetch/store templates somewhere other than in DOM elements.
24.2906 + // Template sources need to have the following functions:
24.2907 + // text() - returns the template text from your storage location
24.2908 + // text(value) - writes the supplied template text to your storage location
24.2909 + // data(key) - reads values stored using data(key, value) - see below
24.2910 + // data(key, value) - associates "value" with this template and the key "key". Is used to store information like "isRewritten".
24.2911 + //
24.2912 + // Optionally, template sources can also have the following functions:
24.2913 + // nodes() - returns a DOM element containing the nodes of this template, where available
24.2914 + // nodes(value) - writes the given DOM element to your storage location
24.2915 + // If a DOM element is available for a given template source, template engines are encouraged to use it in preference over text()
24.2916 + // for improved speed. However, all templateSources must supply text() even if they don't supply nodes().
24.2917 + //
24.2918 + // Once you've implemented a templateSource, make your template engine use it by subclassing whatever template engine you were
24.2919 + // using and overriding "makeTemplateSource" to return an instance of your custom template source.
24.2920 +
24.2921 + ko.templateSources = {};
24.2922 +
24.2923 + // ---- ko.templateSources.domElement -----
24.2924 +
24.2925 + ko.templateSources.domElement = function(element) {
24.2926 + this.domElement = element;
24.2927 + }
24.2928 +
24.2929 + ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
24.2930 + var tagNameLower = ko.utils.tagNameLower(this.domElement),
24.2931 + elemContentsProperty = tagNameLower === "script" ? "text"
24.2932 + : tagNameLower === "textarea" ? "value"
24.2933 + : "innerHTML";
24.2934 +
24.2935 + if (arguments.length == 0) {
24.2936 + return this.domElement[elemContentsProperty];
24.2937 + } else {
24.2938 + var valueToWrite = arguments[0];
24.2939 + if (elemContentsProperty === "innerHTML")
24.2940 + ko.utils.setHtml(this.domElement, valueToWrite);
24.2941 + else
24.2942 + this.domElement[elemContentsProperty] = valueToWrite;
24.2943 + }
24.2944 + };
24.2945 +
24.2946 + ko.templateSources.domElement.prototype['data'] = function(key /*, valueToWrite */) {
24.2947 + if (arguments.length === 1) {
24.2948 + return ko.utils.domData.get(this.domElement, "templateSourceData_" + key);
24.2949 + } else {
24.2950 + ko.utils.domData.set(this.domElement, "templateSourceData_" + key, arguments[1]);
24.2951 + }
24.2952 + };
24.2953 +
24.2954 + // ---- ko.templateSources.anonymousTemplate -----
24.2955 + // Anonymous templates are normally saved/retrieved as DOM nodes through "nodes".
24.2956 + // For compatibility, you can also read "text"; it will be serialized from the nodes on demand.
24.2957 + // Writing to "text" is still supported, but then the template data will not be available as DOM nodes.
24.2958 +
24.2959 + var anonymousTemplatesDomDataKey = "__ko_anon_template__";
24.2960 + ko.templateSources.anonymousTemplate = function(element) {
24.2961 + this.domElement = element;
24.2962 + }
24.2963 + ko.templateSources.anonymousTemplate.prototype = new ko.templateSources.domElement();
24.2964 + ko.templateSources.anonymousTemplate.prototype['text'] = function(/* valueToWrite */) {
24.2965 + if (arguments.length == 0) {
24.2966 + var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
24.2967 + if (templateData.textData === undefined && templateData.containerData)
24.2968 + templateData.textData = templateData.containerData.innerHTML;
24.2969 + return templateData.textData;
24.2970 + } else {
24.2971 + var valueToWrite = arguments[0];
24.2972 + ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {textData: valueToWrite});
24.2973 + }
24.2974 + };
24.2975 + ko.templateSources.domElement.prototype['nodes'] = function(/* valueToWrite */) {
24.2976 + if (arguments.length == 0) {
24.2977 + var templateData = ko.utils.domData.get(this.domElement, anonymousTemplatesDomDataKey) || {};
24.2978 + return templateData.containerData;
24.2979 + } else {
24.2980 + var valueToWrite = arguments[0];
24.2981 + ko.utils.domData.set(this.domElement, anonymousTemplatesDomDataKey, {containerData: valueToWrite});
24.2982 + }
24.2983 + };
24.2984 +
24.2985 + ko.exportSymbol('templateSources', ko.templateSources);
24.2986 + ko.exportSymbol('templateSources.domElement', ko.templateSources.domElement);
24.2987 + ko.exportSymbol('templateSources.anonymousTemplate', ko.templateSources.anonymousTemplate);
24.2988 +})();
24.2989 +(function () {
24.2990 + var _templateEngine;
24.2991 + ko.setTemplateEngine = function (templateEngine) {
24.2992 + if ((templateEngine != undefined) && !(templateEngine instanceof ko.templateEngine))
24.2993 + throw new Error("templateEngine must inherit from ko.templateEngine");
24.2994 + _templateEngine = templateEngine;
24.2995 + }
24.2996 +
24.2997 + function invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, action) {
24.2998 + var node, nextInQueue = firstNode, firstOutOfRangeNode = ko.virtualElements.nextSibling(lastNode);
24.2999 + while (nextInQueue && ((node = nextInQueue) !== firstOutOfRangeNode)) {
24.3000 + nextInQueue = ko.virtualElements.nextSibling(node);
24.3001 + if (node.nodeType === 1 || node.nodeType === 8)
24.3002 + action(node);
24.3003 + }
24.3004 + }
24.3005 +
24.3006 + function activateBindingsOnContinuousNodeArray(continuousNodeArray, bindingContext) {
24.3007 + // To be used on any nodes that have been rendered by a template and have been inserted into some parent element
24.3008 + // Walks through continuousNodeArray (which *must* be continuous, i.e., an uninterrupted sequence of sibling nodes, because
24.3009 + // the algorithm for walking them relies on this), and for each top-level item in the virtual-element sense,
24.3010 + // (1) Does a regular "applyBindings" to associate bindingContext with this node and to activate any non-memoized bindings
24.3011 + // (2) Unmemoizes any memos in the DOM subtree (e.g., to activate bindings that had been memoized during template rewriting)
24.3012 +
24.3013 + if (continuousNodeArray.length) {
24.3014 + var firstNode = continuousNodeArray[0], lastNode = continuousNodeArray[continuousNodeArray.length - 1];
24.3015 +
24.3016 + // Need to applyBindings *before* unmemoziation, because unmemoization might introduce extra nodes (that we don't want to re-bind)
24.3017 + // whereas a regular applyBindings won't introduce new memoized nodes
24.3018 + invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, function(node) {
24.3019 + ko.applyBindings(bindingContext, node);
24.3020 + });
24.3021 + invokeForEachNodeOrCommentInContinuousRange(firstNode, lastNode, function(node) {
24.3022 + ko.memoization.unmemoizeDomNodeAndDescendants(node, [bindingContext]);
24.3023 + });
24.3024 + }
24.3025 + }
24.3026 +
24.3027 + function getFirstNodeFromPossibleArray(nodeOrNodeArray) {
24.3028 + return nodeOrNodeArray.nodeType ? nodeOrNodeArray
24.3029 + : nodeOrNodeArray.length > 0 ? nodeOrNodeArray[0]
24.3030 + : null;
24.3031 + }
24.3032 +
24.3033 + function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
24.3034 + options = options || {};
24.3035 + var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
24.3036 + var templateDocument = firstTargetNode && firstTargetNode.ownerDocument;
24.3037 + var templateEngineToUse = (options['templateEngine'] || _templateEngine);
24.3038 + ko.templateRewriting.ensureTemplateIsRewritten(template, templateEngineToUse, templateDocument);
24.3039 + var renderedNodesArray = templateEngineToUse['renderTemplate'](template, bindingContext, options, templateDocument);
24.3040 +
24.3041 + // Loosely check result is an array of DOM nodes
24.3042 + if ((typeof renderedNodesArray.length != "number") || (renderedNodesArray.length > 0 && typeof renderedNodesArray[0].nodeType != "number"))
24.3043 + throw new Error("Template engine must return an array of DOM nodes");
24.3044 +
24.3045 + var haveAddedNodesToParent = false;
24.3046 + switch (renderMode) {
24.3047 + case "replaceChildren":
24.3048 + ko.virtualElements.setDomNodeChildren(targetNodeOrNodeArray, renderedNodesArray);
24.3049 + haveAddedNodesToParent = true;
24.3050 + break;
24.3051 + case "replaceNode":
24.3052 + ko.utils.replaceDomNodes(targetNodeOrNodeArray, renderedNodesArray);
24.3053 + haveAddedNodesToParent = true;
24.3054 + break;
24.3055 + case "ignoreTargetNode": break;
24.3056 + default:
24.3057 + throw new Error("Unknown renderMode: " + renderMode);
24.3058 + }
24.3059 +
24.3060 + if (haveAddedNodesToParent) {
24.3061 + activateBindingsOnContinuousNodeArray(renderedNodesArray, bindingContext);
24.3062 + if (options['afterRender'])
24.3063 + ko.dependencyDetection.ignore(options['afterRender'], null, [renderedNodesArray, bindingContext['$data']]);
24.3064 + }
24.3065 +
24.3066 + return renderedNodesArray;
24.3067 + }
24.3068 +
24.3069 + ko.renderTemplate = function (template, dataOrBindingContext, options, targetNodeOrNodeArray, renderMode) {
24.3070 + options = options || {};
24.3071 + if ((options['templateEngine'] || _templateEngine) == undefined)
24.3072 + throw new Error("Set a template engine before calling renderTemplate");
24.3073 + renderMode = renderMode || "replaceChildren";
24.3074 +
24.3075 + if (targetNodeOrNodeArray) {
24.3076 + var firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
24.3077 +
24.3078 + var whenToDispose = function () { return (!firstTargetNode) || !ko.utils.domNodeIsAttachedToDocument(firstTargetNode); }; // Passive disposal (on next evaluation)
24.3079 + var activelyDisposeWhenNodeIsRemoved = (firstTargetNode && renderMode == "replaceNode") ? firstTargetNode.parentNode : firstTargetNode;
24.3080 +
24.3081 + return ko.dependentObservable( // So the DOM is automatically updated when any dependency changes
24.3082 + function () {
24.3083 + // Ensure we've got a proper binding context to work with
24.3084 + var bindingContext = (dataOrBindingContext && (dataOrBindingContext instanceof ko.bindingContext))
24.3085 + ? dataOrBindingContext
24.3086 + : new ko.bindingContext(ko.utils.unwrapObservable(dataOrBindingContext));
24.3087 +
24.3088 + // Support selecting template as a function of the data being rendered
24.3089 + var templateName = typeof(template) == 'function' ? template(bindingContext['$data'], bindingContext) : template;
24.3090 +
24.3091 + var renderedNodesArray = executeTemplate(targetNodeOrNodeArray, renderMode, templateName, bindingContext, options);
24.3092 + if (renderMode == "replaceNode") {
24.3093 + targetNodeOrNodeArray = renderedNodesArray;
24.3094 + firstTargetNode = getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
24.3095 + }
24.3096 + },
24.3097 + null,
24.3098 + { disposeWhen: whenToDispose, disposeWhenNodeIsRemoved: activelyDisposeWhenNodeIsRemoved }
24.3099 + );
24.3100 + } else {
24.3101 + // 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
24.3102 + return ko.memoization.memoize(function (domNode) {
24.3103 + ko.renderTemplate(template, dataOrBindingContext, options, domNode, "replaceNode");
24.3104 + });
24.3105 + }
24.3106 + };
24.3107 +
24.3108 + ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
24.3109 + // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
24.3110 + // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
24.3111 + var arrayItemContext;
24.3112 +
24.3113 + // This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
24.3114 + var executeTemplateForArrayItem = function (arrayValue, index) {
24.3115 + // Support selecting template as a function of the data being rendered
24.3116 + arrayItemContext = parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue), options['as']);
24.3117 + arrayItemContext['$index'] = index;
24.3118 + var templateName = typeof(template) == 'function' ? template(arrayValue, arrayItemContext) : template;
24.3119 + return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
24.3120 + }
24.3121 +
24.3122 + // This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
24.3123 + var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
24.3124 + activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
24.3125 + if (options['afterRender'])
24.3126 + options['afterRender'](addedNodesArray, arrayValue);
24.3127 + };
24.3128 +
24.3129 + return ko.dependentObservable(function () {
24.3130 + var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
24.3131 + if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
24.3132 + unwrappedArray = [unwrappedArray];
24.3133 +
24.3134 + // Filter out any entries marked as destroyed
24.3135 + var filteredArray = ko.utils.arrayFilter(unwrappedArray, function(item) {
24.3136 + return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
24.3137 + });
24.3138 +
24.3139 + // Call setDomNodeChildrenFromArrayMapping, ignoring any observables unwrapped within (most likely from a callback function).
24.3140 + // If the array items are observables, though, they will be unwrapped in executeTemplateForArrayItem and managed within setDomNodeChildrenFromArrayMapping.
24.3141 + ko.dependencyDetection.ignore(ko.utils.setDomNodeChildrenFromArrayMapping, null, [targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback]);
24.3142 +
24.3143 + }, null, { disposeWhenNodeIsRemoved: targetNode });
24.3144 + };
24.3145 +
24.3146 + var templateComputedDomDataKey = '__ko__templateComputedDomDataKey__';
24.3147 + function disposeOldComputedAndStoreNewOne(element, newComputed) {
24.3148 + var oldComputed = ko.utils.domData.get(element, templateComputedDomDataKey);
24.3149 + if (oldComputed && (typeof(oldComputed.dispose) == 'function'))
24.3150 + oldComputed.dispose();
24.3151 + ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
24.3152 + }
24.3153 +
24.3154 + ko.bindingHandlers['template'] = {
24.3155 + 'init': function(element, valueAccessor) {
24.3156 + // Support anonymous templates
24.3157 + var bindingValue = ko.utils.unwrapObservable(valueAccessor());
24.3158 + if ((typeof bindingValue != "string") && (!bindingValue['name']) && (element.nodeType == 1 || element.nodeType == 8)) {
24.3159 + // It's an anonymous template - store the element contents, then clear the element
24.3160 + var templateNodes = element.nodeType == 1 ? element.childNodes : ko.virtualElements.childNodes(element),
24.3161 + container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
24.3162 + new ko.templateSources.anonymousTemplate(element)['nodes'](container);
24.3163 + }
24.3164 + return { 'controlsDescendantBindings': true };
24.3165 + },
24.3166 + 'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
24.3167 + var templateName = ko.utils.unwrapObservable(valueAccessor()),
24.3168 + options = {},
24.3169 + shouldDisplay = true,
24.3170 + dataValue,
24.3171 + templateComputed = null;
24.3172 +
24.3173 + if (typeof templateName != "string") {
24.3174 + options = templateName;
24.3175 + templateName = options['name'];
24.3176 +
24.3177 + // Support "if"/"ifnot" conditions
24.3178 + if ('if' in options)
24.3179 + shouldDisplay = ko.utils.unwrapObservable(options['if']);
24.3180 + if (shouldDisplay && 'ifnot' in options)
24.3181 + shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);
24.3182 +
24.3183 + dataValue = ko.utils.unwrapObservable(options['data']);
24.3184 + }
24.3185 +
24.3186 + if ('foreach' in options) {
24.3187 + // Render once for each data point (treating data set as empty if shouldDisplay==false)
24.3188 + var dataArray = (shouldDisplay && options['foreach']) || [];
24.3189 + templateComputed = ko.renderTemplateForEach(templateName || element, dataArray, options, element, bindingContext);
24.3190 + } else if (!shouldDisplay) {
24.3191 + ko.virtualElements.emptyNode(element);
24.3192 + } else {
24.3193 + // Render once for this single data point (or use the viewModel if no data was provided)
24.3194 + var innerBindingContext = ('data' in options) ?
24.3195 + bindingContext['createChildContext'](dataValue, options['as']) : // Given an explitit 'data' value, we create a child binding context for it
24.3196 + bindingContext; // Given no explicit 'data' value, we retain the same binding context
24.3197 + templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
24.3198 + }
24.3199 +
24.3200 + // It only makes sense to have a single template computed per element (otherwise which one should have its output displayed?)
24.3201 + disposeOldComputedAndStoreNewOne(element, templateComputed);
24.3202 + }
24.3203 + };
24.3204 +
24.3205 + // Anonymous templates can't be rewritten. Give a nice error message if you try to do it.
24.3206 + ko.expressionRewriting.bindingRewriteValidators['template'] = function(bindingValue) {
24.3207 + var parsedBindingValue = ko.expressionRewriting.parseObjectLiteral(bindingValue);
24.3208 +
24.3209 + if ((parsedBindingValue.length == 1) && parsedBindingValue[0]['unknown'])
24.3210 + return null; // It looks like a string literal, not an object literal, so treat it as a named template (which is allowed for rewriting)
24.3211 +
24.3212 + if (ko.expressionRewriting.keyValueArrayContainsKey(parsedBindingValue, "name"))
24.3213 + return null; // Named templates can be rewritten, so return "no error"
24.3214 + return "This template engine does not support anonymous templates nested within its templates";
24.3215 + };
24.3216 +
24.3217 + ko.virtualElements.allowedBindings['template'] = true;
24.3218 +})();
24.3219 +
24.3220 +ko.exportSymbol('setTemplateEngine', ko.setTemplateEngine);
24.3221 +ko.exportSymbol('renderTemplate', ko.renderTemplate);
24.3222 +
24.3223 +ko.utils.compareArrays = (function () {
24.3224 + var statusNotInOld = 'added', statusNotInNew = 'deleted';
24.3225 +
24.3226 + // Simple calculation based on Levenshtein distance.
24.3227 + function compareArrays(oldArray, newArray, dontLimitMoves) {
24.3228 + oldArray = oldArray || [];
24.3229 + newArray = newArray || [];
24.3230 +
24.3231 + if (oldArray.length <= newArray.length)
24.3232 + return compareSmallArrayToBigArray(oldArray, newArray, statusNotInOld, statusNotInNew, dontLimitMoves);
24.3233 + else
24.3234 + return compareSmallArrayToBigArray(newArray, oldArray, statusNotInNew, statusNotInOld, dontLimitMoves);
24.3235 + }
24.3236 +
24.3237 + function compareSmallArrayToBigArray(smlArray, bigArray, statusNotInSml, statusNotInBig, dontLimitMoves) {
24.3238 + var myMin = Math.min,
24.3239 + myMax = Math.max,
24.3240 + editDistanceMatrix = [],
24.3241 + smlIndex, smlIndexMax = smlArray.length,
24.3242 + bigIndex, bigIndexMax = bigArray.length,
24.3243 + compareRange = (bigIndexMax - smlIndexMax) || 1,
24.3244 + maxDistance = smlIndexMax + bigIndexMax + 1,
24.3245 + thisRow, lastRow,
24.3246 + bigIndexMaxForRow, bigIndexMinForRow;
24.3247 +
24.3248 + for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
24.3249 + lastRow = thisRow;
24.3250 + editDistanceMatrix.push(thisRow = []);
24.3251 + bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
24.3252 + bigIndexMinForRow = myMax(0, smlIndex - 1);
24.3253 + for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) {
24.3254 + if (!bigIndex)
24.3255 + thisRow[bigIndex] = smlIndex + 1;
24.3256 + else if (!smlIndex) // Top row - transform empty array into new array via additions
24.3257 + thisRow[bigIndex] = bigIndex + 1;
24.3258 + else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1])
24.3259 + thisRow[bigIndex] = lastRow[bigIndex - 1]; // copy value (no edit)
24.3260 + else {
24.3261 + var northDistance = lastRow[bigIndex] || maxDistance; // not in big (deletion)
24.3262 + var westDistance = thisRow[bigIndex - 1] || maxDistance; // not in small (addition)
24.3263 + thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
24.3264 + }
24.3265 + }
24.3266 + }
24.3267 +
24.3268 + var editScript = [], meMinusOne, notInSml = [], notInBig = [];
24.3269 + for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
24.3270 + meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
24.3271 + if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
24.3272 + notInSml.push(editScript[editScript.length] = { // added
24.3273 + 'status': statusNotInSml,
24.3274 + 'value': bigArray[--bigIndex],
24.3275 + 'index': bigIndex });
24.3276 + } else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) {
24.3277 + notInBig.push(editScript[editScript.length] = { // deleted
24.3278 + 'status': statusNotInBig,
24.3279 + 'value': smlArray[--smlIndex],
24.3280 + 'index': smlIndex });
24.3281 + } else {
24.3282 + editScript.push({
24.3283 + 'status': "retained",
24.3284 + 'value': bigArray[--bigIndex] });
24.3285 + --smlIndex;
24.3286 + }
24.3287 + }
24.3288 +
24.3289 + if (notInSml.length && notInBig.length) {
24.3290 + // Set a limit on the number of consecutive non-matching comparisons; having it a multiple of
24.3291 + // smlIndexMax keeps the time complexity of this algorithm linear.
24.3292 + var limitFailedCompares = smlIndexMax * 10, failedCompares,
24.3293 + a, d, notInSmlItem, notInBigItem;
24.3294 + // Go through the items that have been added and deleted and try to find matches between them.
24.3295 + for (failedCompares = a = 0; (dontLimitMoves || failedCompares < limitFailedCompares) && (notInSmlItem = notInSml[a]); a++) {
24.3296 + for (d = 0; notInBigItem = notInBig[d]; d++) {
24.3297 + if (notInSmlItem['value'] === notInBigItem['value']) {
24.3298 + notInSmlItem['moved'] = notInBigItem['index'];
24.3299 + notInBigItem['moved'] = notInSmlItem['index'];
24.3300 + notInBig.splice(d,1); // This item is marked as moved; so remove it from notInBig list
24.3301 + failedCompares = d = 0; // Reset failed compares count because we're checking for consecutive failures
24.3302 + break;
24.3303 + }
24.3304 + }
24.3305 + failedCompares += d;
24.3306 + }
24.3307 + }
24.3308 + return editScript.reverse();
24.3309 + }
24.3310 +
24.3311 + return compareArrays;
24.3312 +})();
24.3313 +
24.3314 +ko.exportSymbol('utils.compareArrays', ko.utils.compareArrays);
24.3315 +
24.3316 +(function () {
24.3317 + // Objective:
24.3318 + // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
24.3319 + // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
24.3320 + // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
24.3321 + // 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
24.3322 + // previously mapped - retain those nodes, and just insert/delete other ones
24.3323 +
24.3324 + // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
24.3325 + // You can use this, for example, to activate bindings on those nodes.
24.3326 +
24.3327 + function fixUpNodesToBeMovedOrRemoved(contiguousNodeArray) {
24.3328 + // Before moving, deleting, or replacing a set of nodes that were previously outputted by the "map" function, we have to reconcile
24.3329 + // them against what is in the DOM right now. It may be that some of the nodes have already been removed from the document,
24.3330 + // or that new nodes might have been inserted in the middle, for example by a binding. Also, there may previously have been
24.3331 + // leading comment nodes (created by rewritten string-based templates) that have since been removed during binding.
24.3332 + // So, this function translates the old "map" output array into its best guess of what set of current DOM nodes should be removed.
24.3333 + //
24.3334 + // Rules:
24.3335 + // [A] Any leading nodes that aren't in the document any more should be ignored
24.3336 + // These most likely correspond to memoization nodes that were already removed during binding
24.3337 + // See https://github.com/SteveSanderson/knockout/pull/440
24.3338 + // [B] We want to output a contiguous series of nodes that are still in the document. So, ignore any nodes that
24.3339 + // have already been removed, and include any nodes that have been inserted among the previous collection
24.3340 +
24.3341 + // Rule [A]
24.3342 + while (contiguousNodeArray.length && !ko.utils.domNodeIsAttachedToDocument(contiguousNodeArray[0]))
24.3343 + contiguousNodeArray.splice(0, 1);
24.3344 +
24.3345 + // Rule [B]
24.3346 + if (contiguousNodeArray.length > 1) {
24.3347 + // Build up the actual new contiguous node set
24.3348 + var current = contiguousNodeArray[0], last = contiguousNodeArray[contiguousNodeArray.length - 1], newContiguousSet = [current];
24.3349 + while (current !== last) {
24.3350 + current = current.nextSibling;
24.3351 + if (!current) // Won't happen, except if the developer has manually removed some DOM elements (then we're in an undefined scenario)
24.3352 + return;
24.3353 + newContiguousSet.push(current);
24.3354 + }
24.3355 +
24.3356 + // ... then mutate the input array to match this.
24.3357 + // (The following line replaces the contents of contiguousNodeArray with newContiguousSet)
24.3358 + Array.prototype.splice.apply(contiguousNodeArray, [0, contiguousNodeArray.length].concat(newContiguousSet));
24.3359 + }
24.3360 + return contiguousNodeArray;
24.3361 + }
24.3362 +
24.3363 + function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
24.3364 + // Map this array value inside a dependentObservable so we re-map when any dependency changes
24.3365 + var mappedNodes = [];
24.3366 + var dependentObservable = ko.dependentObservable(function() {
24.3367 + var newMappedNodes = mapping(valueToMap, index) || [];
24.3368 +
24.3369 + // On subsequent evaluations, just replace the previously-inserted DOM nodes
24.3370 + if (mappedNodes.length > 0) {
24.3371 + ko.utils.replaceDomNodes(fixUpNodesToBeMovedOrRemoved(mappedNodes), newMappedNodes);
24.3372 + if (callbackAfterAddingNodes)
24.3373 + ko.dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]);
24.3374 + }
24.3375 +
24.3376 + // Replace the contents of the mappedNodes array, thereby updating the record
24.3377 + // of which nodes would be deleted if valueToMap was itself later removed
24.3378 + mappedNodes.splice(0, mappedNodes.length);
24.3379 + ko.utils.arrayPushAll(mappedNodes, newMappedNodes);
24.3380 + }, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function() { return (mappedNodes.length == 0) || !ko.utils.domNodeIsAttachedToDocument(mappedNodes[0]) } });
24.3381 + return { mappedNodes : mappedNodes, dependentObservable : (dependentObservable.isActive() ? dependentObservable : undefined) };
24.3382 + }
24.3383 +
24.3384 + var lastMappingResultDomDataKey = "setDomNodeChildrenFromArrayMapping_lastMappingResult";
24.3385 +
24.3386 + ko.utils.setDomNodeChildrenFromArrayMapping = function (domNode, array, mapping, options, callbackAfterAddingNodes) {
24.3387 + // Compare the provided array against the previous one
24.3388 + array = array || [];
24.3389 + options = options || {};
24.3390 + var isFirstExecution = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) === undefined;
24.3391 + var lastMappingResult = ko.utils.domData.get(domNode, lastMappingResultDomDataKey) || [];
24.3392 + var lastArray = ko.utils.arrayMap(lastMappingResult, function (x) { return x.arrayEntry; });
24.3393 + var editScript = ko.utils.compareArrays(lastArray, array);
24.3394 +
24.3395 + // Build the new mapping result
24.3396 + var newMappingResult = [];
24.3397 + var lastMappingResultIndex = 0;
24.3398 + var newMappingResultIndex = 0;
24.3399 +
24.3400 + var nodesToDelete = [];
24.3401 + var itemsToProcess = [];
24.3402 + var itemsForBeforeRemoveCallbacks = [];
24.3403 + var itemsForMoveCallbacks = [];
24.3404 + var itemsForAfterAddCallbacks = [];
24.3405 + var mapData;
24.3406 +
24.3407 + function itemMovedOrRetained(editScriptIndex, oldPosition) {
24.3408 + mapData = lastMappingResult[oldPosition];
24.3409 + if (newMappingResultIndex !== oldPosition)
24.3410 + itemsForMoveCallbacks[editScriptIndex] = mapData;
24.3411 + // Since updating the index might change the nodes, do so before calling fixUpNodesToBeMovedOrRemoved
24.3412 + mapData.indexObservable(newMappingResultIndex++);
24.3413 + fixUpNodesToBeMovedOrRemoved(mapData.mappedNodes);
24.3414 + newMappingResult.push(mapData);
24.3415 + itemsToProcess.push(mapData);
24.3416 + }
24.3417 +
24.3418 + function callCallback(callback, items) {
24.3419 + if (callback) {
24.3420 + for (var i = 0, n = items.length; i < n; i++) {
24.3421 + if (items[i]) {
24.3422 + ko.utils.arrayForEach(items[i].mappedNodes, function(node) {
24.3423 + callback(node, i, items[i].arrayEntry);
24.3424 + });
24.3425 + }
24.3426 + }
24.3427 + }
24.3428 + }
24.3429 +
24.3430 + for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
24.3431 + movedIndex = editScriptItem['moved'];
24.3432 + switch (editScriptItem['status']) {
24.3433 + case "deleted":
24.3434 + if (movedIndex === undefined) {
24.3435 + mapData = lastMappingResult[lastMappingResultIndex];
24.3436 +
24.3437 + // Stop tracking changes to the mapping for these nodes
24.3438 + if (mapData.dependentObservable)
24.3439 + mapData.dependentObservable.dispose();
24.3440 +
24.3441 + // Queue these nodes for later removal
24.3442 + nodesToDelete.push.apply(nodesToDelete, fixUpNodesToBeMovedOrRemoved(mapData.mappedNodes));
24.3443 + if (options['beforeRemove']) {
24.3444 + itemsForBeforeRemoveCallbacks[i] = mapData;
24.3445 + itemsToProcess.push(mapData);
24.3446 + }
24.3447 + }
24.3448 + lastMappingResultIndex++;
24.3449 + break;
24.3450 +
24.3451 + case "retained":
24.3452 + itemMovedOrRetained(i, lastMappingResultIndex++);
24.3453 + break;
24.3454 +
24.3455 + case "added":
24.3456 + if (movedIndex !== undefined) {
24.3457 + itemMovedOrRetained(i, movedIndex);
24.3458 + } else {
24.3459 + mapData = { arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++) };
24.3460 + newMappingResult.push(mapData);
24.3461 + itemsToProcess.push(mapData);
24.3462 + if (!isFirstExecution)
24.3463 + itemsForAfterAddCallbacks[i] = mapData;
24.3464 + }
24.3465 + break;
24.3466 + }
24.3467 + }
24.3468 +
24.3469 + // Call beforeMove first before any changes have been made to the DOM
24.3470 + callCallback(options['beforeMove'], itemsForMoveCallbacks);
24.3471 +
24.3472 + // Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
24.3473 + ko.utils.arrayForEach(nodesToDelete, options['beforeRemove'] ? ko.cleanNode : ko.removeNode);
24.3474 +
24.3475 + // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
24.3476 + for (var i = 0, nextNode = ko.virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
24.3477 + // Get nodes for newly added items
24.3478 + if (!mapData.mappedNodes)
24.3479 + ko.utils.extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable));
24.3480 +
24.3481 + // Put nodes in the right place if they aren't there already
24.3482 + for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
24.3483 + if (node !== nextNode)
24.3484 + ko.virtualElements.insertAfter(domNode, node, lastNode);
24.3485 + }
24.3486 +
24.3487 + // Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
24.3488 + if (!mapData.initialized && callbackAfterAddingNodes) {
24.3489 + callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
24.3490 + mapData.initialized = true;
24.3491 + }
24.3492 + }
24.3493 +
24.3494 + // If there's a beforeRemove callback, call it after reordering.
24.3495 + // Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
24.3496 + // some sort of animation, which is why we first reorder the nodes that will be removed. If the
24.3497 + // callback instead removes the nodes right away, it would be more efficient to skip reordering them.
24.3498 + // Perhaps we'll make that change in the future if this scenario becomes more common.
24.3499 + callCallback(options['beforeRemove'], itemsForBeforeRemoveCallbacks);
24.3500 +
24.3501 + // Finally call afterMove and afterAdd callbacks
24.3502 + callCallback(options['afterMove'], itemsForMoveCallbacks);
24.3503 + callCallback(options['afterAdd'], itemsForAfterAddCallbacks);
24.3504 +
24.3505 + // Store a copy of the array items we just considered so we can difference it next time
24.3506 + ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
24.3507 + }
24.3508 +})();
24.3509 +
24.3510 +ko.exportSymbol('utils.setDomNodeChildrenFromArrayMapping', ko.utils.setDomNodeChildrenFromArrayMapping);
24.3511 +ko.nativeTemplateEngine = function () {
24.3512 + this['allowTemplateRewriting'] = false;
24.3513 +}
24.3514 +
24.3515 +ko.nativeTemplateEngine.prototype = new ko.templateEngine();
24.3516 +ko.nativeTemplateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
24.3517 + var useNodesIfAvailable = !(ko.utils.ieVersion < 9), // IE<9 cloneNode doesn't work properly
24.3518 + templateNodesFunc = useNodesIfAvailable ? templateSource['nodes'] : null,
24.3519 + templateNodes = templateNodesFunc ? templateSource['nodes']() : null;
24.3520 +
24.3521 + if (templateNodes) {
24.3522 + return ko.utils.makeArray(templateNodes.cloneNode(true).childNodes);
24.3523 + } else {
24.3524 + var templateText = templateSource['text']();
24.3525 + return ko.utils.parseHtmlFragment(templateText);
24.3526 + }
24.3527 +};
24.3528 +
24.3529 +ko.nativeTemplateEngine.instance = new ko.nativeTemplateEngine();
24.3530 +ko.setTemplateEngine(ko.nativeTemplateEngine.instance);
24.3531 +
24.3532 +ko.exportSymbol('nativeTemplateEngine', ko.nativeTemplateEngine);
24.3533 +(function() {
24.3534 + ko.jqueryTmplTemplateEngine = function () {
24.3535 + // Detect which version of jquery-tmpl you're using. Unfortunately jquery-tmpl
24.3536 + // doesn't expose a version number, so we have to infer it.
24.3537 + // Note that as of Knockout 1.3, we only support jQuery.tmpl 1.0.0pre and later,
24.3538 + // which KO internally refers to as version "2", so older versions are no longer detected.
24.3539 + var jQueryTmplVersion = this.jQueryTmplVersion = (function() {
24.3540 + if ((typeof(jQuery) == "undefined") || !(jQuery['tmpl']))
24.3541 + return 0;
24.3542 + // Since it exposes no official version number, we use our own numbering system. To be updated as jquery-tmpl evolves.
24.3543 + try {
24.3544 + if (jQuery['tmpl']['tag']['tmpl']['open'].toString().indexOf('__') >= 0) {
24.3545 + // Since 1.0.0pre, custom tags should append markup to an array called "__"
24.3546 + return 2; // Final version of jquery.tmpl
24.3547 + }
24.3548 + } catch(ex) { /* Apparently not the version we were looking for */ }
24.3549 +
24.3550 + return 1; // Any older version that we don't support
24.3551 + })();
24.3552 +
24.3553 + function ensureHasReferencedJQueryTemplates() {
24.3554 + if (jQueryTmplVersion < 2)
24.3555 + throw new Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");
24.3556 + }
24.3557 +
24.3558 + function executeTemplate(compiledTemplate, data, jQueryTemplateOptions) {
24.3559 + return jQuery['tmpl'](compiledTemplate, data, jQueryTemplateOptions);
24.3560 + }
24.3561 +
24.3562 + this['renderTemplateSource'] = function(templateSource, bindingContext, options) {
24.3563 + options = options || {};
24.3564 + ensureHasReferencedJQueryTemplates();
24.3565 +
24.3566 + // Ensure we have stored a precompiled version of this template (don't want to reparse on every render)
24.3567 + var precompiled = templateSource['data']('precompiled');
24.3568 + if (!precompiled) {
24.3569 + var templateText = templateSource['text']() || "";
24.3570 + // Wrap in "with($whatever.koBindingContext) { ... }"
24.3571 + templateText = "{{ko_with $item.koBindingContext}}" + templateText + "{{/ko_with}}";
24.3572 +
24.3573 + precompiled = jQuery['template'](null, templateText);
24.3574 + templateSource['data']('precompiled', precompiled);
24.3575 + }
24.3576 +
24.3577 + var data = [bindingContext['$data']]; // Prewrap the data in an array to stop jquery.tmpl from trying to unwrap any arrays
24.3578 + var jQueryTemplateOptions = jQuery['extend']({ 'koBindingContext': bindingContext }, options['templateOptions']);
24.3579 +
24.3580 + var resultNodes = executeTemplate(precompiled, data, jQueryTemplateOptions);
24.3581 + resultNodes['appendTo'](document.createElement("div")); // Using "appendTo" forces jQuery/jQuery.tmpl to perform necessary cleanup work
24.3582 +
24.3583 + jQuery['fragments'] = {}; // Clear jQuery's fragment cache to avoid a memory leak after a large number of template renders
24.3584 + return resultNodes;
24.3585 + };
24.3586 +
24.3587 + this['createJavaScriptEvaluatorBlock'] = function(script) {
24.3588 + return "{{ko_code ((function() { return " + script + " })()) }}";
24.3589 + };
24.3590 +
24.3591 + this['addTemplate'] = function(templateName, templateMarkup) {
24.3592 + document.write("<script type='text/html' id='" + templateName + "'>" + templateMarkup + "</script>");
24.3593 + };
24.3594 +
24.3595 + if (jQueryTmplVersion > 0) {
24.3596 + jQuery['tmpl']['tag']['ko_code'] = {
24.3597 + open: "__.push($1 || '');"
24.3598 + };
24.3599 + jQuery['tmpl']['tag']['ko_with'] = {
24.3600 + open: "with($1) {",
24.3601 + close: "} "
24.3602 + };
24.3603 + }
24.3604 + };
24.3605 +
24.3606 + ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine();
24.3607 +
24.3608 + // Use this one by default *only if jquery.tmpl is referenced*
24.3609 + var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine();
24.3610 + if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0)
24.3611 + ko.setTemplateEngine(jqueryTmplTemplateEngineInstance);
24.3612 +
24.3613 + ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine);
24.3614 +})();
24.3615 +});
24.3616 +})(window,document,navigator,window["jQuery"]);
24.3617 +})();
24.3618 \ No newline at end of file
25.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
25.2 +++ b/ko-bck2brwsr/src/test/java/org/apidesign/html/ko2brwsr/Bck2BrwsrKnockoutTest.java Mon Jun 24 17:49:27 2013 +0200
25.3 @@ -0,0 +1,124 @@
25.4 +/**
25.5 + * HTML via Java(tm) Language Bindings
25.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
25.7 + *
25.8 + * This program is free software: you can redistribute it and/or modify
25.9 + * it under the terms of the GNU General Public License as published by
25.10 + * the Free Software Foundation, version 2 of the License.
25.11 + *
25.12 + * This program is distributed in the hope that it will be useful,
25.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
25.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25.15 + * GNU General Public License for more details. apidesign.org
25.16 + * designates this particular file as subject to the
25.17 + * "Classpath" exception as provided by apidesign.org
25.18 + * in the License file that accompanied this code.
25.19 + *
25.20 + * You should have received a copy of the GNU General Public License
25.21 + * along with this program. Look for COPYING file in the top folder.
25.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
25.23 + */
25.24 +package org.apidesign.html.ko2brwsr;
25.25 +
25.26 +import java.io.BufferedReader;
25.27 +import java.io.IOException;
25.28 +import java.io.InputStreamReader;
25.29 +import java.net.URI;
25.30 +import java.net.URISyntaxException;
25.31 +import java.net.URL;
25.32 +import java.net.URLConnection;
25.33 +import java.util.Map;
25.34 +import net.java.html.BrwsrCtx;
25.35 +import org.apidesign.bck2brwsr.core.JavaScriptBody;
25.36 +import org.apidesign.bck2brwsr.vmtest.VMTest;
25.37 +import org.apidesign.html.context.spi.Contexts;
25.38 +import org.apidesign.html.json.spi.Technology;
25.39 +import org.apidesign.html.json.spi.Transfer;
25.40 +import org.apidesign.html.json.tck.KOTest;
25.41 +import org.apidesign.html.json.tck.KnockoutTCK;
25.42 +import org.openide.util.lookup.ServiceProvider;
25.43 +import org.testng.annotations.Factory;
25.44 +
25.45 +/**
25.46 + *
25.47 + * @author Jaroslav Tulach <jtulach@netbeans.org>
25.48 + */
25.49 +@ServiceProvider(service = KnockoutTCK.class)
25.50 +public final class Bck2BrwsrKnockoutTest extends KnockoutTCK {
25.51 + @Factory public static Object[] create() {
25.52 + return VMTest.newTests().
25.53 + withClasses(testClasses()).
25.54 + withLaunchers("bck2brwsr").
25.55 + withTestAnnotation(KOTest.class).
25.56 + build();
25.57 + }
25.58 +
25.59 + @Override
25.60 + public BrwsrCtx createContext() {
25.61 + return Contexts.newBuilder().
25.62 + register(Transfer.class, BrwsrCtxImpl.DEFAULT, 9).
25.63 + register(Technology.class, BrwsrCtxImpl.DEFAULT, 9).build();
25.64 + }
25.65 +
25.66 +
25.67 +
25.68 + @Override
25.69 + public Object createJSON(Map<String, Object> values) {
25.70 + Object json = createJSON();
25.71 +
25.72 + for (Map.Entry<String, Object> entry : values.entrySet()) {
25.73 + putValue(json, entry.getKey(), entry.getValue());
25.74 + }
25.75 + return json;
25.76 + }
25.77 +
25.78 + @JavaScriptBody(args = {}, body = "return new Object();")
25.79 + private static native Object createJSON();
25.80 +
25.81 + @JavaScriptBody(args = { "json", "key", "value" }, body = "json[key] = value;")
25.82 + private static native void putValue(Object json, String key, Object value);
25.83 +
25.84 + @Override
25.85 + public Object executeScript(String script, Object[] arguments) {
25.86 + return execScript(script, arguments);
25.87 + }
25.88 +
25.89 + @JavaScriptBody(args = { "s", "args" }, body =
25.90 + "var f = new Function(s); return f.apply(null, args);"
25.91 + )
25.92 + private static native Object execScript(String s, Object[] arguments);
25.93 +
25.94 + @JavaScriptBody(args = { }, body =
25.95 + "var h;"
25.96 + + "if (!!window && !!window.location && !!window.location.href)\n"
25.97 + + " h = window.location.href;\n"
25.98 + + "else "
25.99 + + " h = null;"
25.100 + + "return h;\n"
25.101 + )
25.102 + private static native String findBaseURL();
25.103 +
25.104 + @Override
25.105 + public URI prepareURL(String content, String mimeType, String[] parameters) {
25.106 + try {
25.107 + final URL baseURL = new URL(findBaseURL());
25.108 + StringBuilder sb = new StringBuilder();
25.109 + sb.append("/dynamic?mimeType=").append(mimeType);
25.110 + for (int i = 0; i < parameters.length; i++) {
25.111 + sb.append("¶m" + i).append("=").append(parameters[i]);
25.112 + }
25.113 + String mangle = content.replace("\n", "%0a")
25.114 + .replace("\"", "\\\"").replace(" ", "%20");
25.115 + sb.append("&content=").append(mangle);
25.116 +
25.117 + URL query = new URL(baseURL, sb.toString());
25.118 + String uri = (String) query.getContent(new Class[] { String.class });
25.119 + URI connectTo = new URI(uri.trim());
25.120 + return connectTo;
25.121 + } catch (IOException ex) {
25.122 + throw new IllegalStateException(ex);
25.123 + } catch (URISyntaxException ex) {
25.124 + throw new IllegalStateException(ex);
25.125 + }
25.126 + }
25.127 +}