Commit 87d177de authored by Sam Saccone's avatar Sam Saccone

Merge pull request #1427 from mckamey/master

DUEL: App Update (#1110), cancel editing on escape keypress (#789), and routing support (#812)
parents c70a02b6 18bca51b
...@@ -26,7 +26,9 @@ ...@@ -26,7 +26,9 @@
"excludeFiles": [ "excludeFiles": [
"**/node_modules/**", "**/node_modules/**",
"**/bower_components/**", "**/bower_components/**",
"examples/vanilladart/**/*.js" "examples/vanilladart/**/*.js",
"examples/duel/www/**",
"examples/duel/src/main/webapp/js/lib/**"
], ],
"requireSpaceBeforeBlockStatements": true, "requireSpaceBeforeBlockStatements": true,
"requireParenthesesAroundIIFE": true, "requireParenthesesAroundIIFE": true,
......
target/ target/
node_modules/
www/cdn/debug/
{
"name": "todomvc-duel",
"version": "0.0.0",
"dependencies": {
"todomvc-common": "~0.3.0"
}
}
{
"private": true,
"scripts": {
"postinstall": "cp node_modules/todomvc-app-css/*.css src/main/webapp/css/ && cp node_modules/todomvc-common/*.css src/main/webapp/css/ && cp node_modules/todomvc-common/base.js src/main/webapp/js/lib/todos.js"
},
"dependencies": {
"todomvc-app-css": "^2.0.0",
"todomvc-common": "^1.0.1"
}
}
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
xmlns="http://maven.apache.org/POM/4.0.0" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.example.todos</groupId> <groupId>com.todomvc.duel</groupId>
<artifactId>todomvc</artifactId> <artifactId>todomvc</artifactId>
<version>0.1.0</version> <version>0.2.0</version>
<packaging>war</packaging> <packaging>war</packaging>
<name>TodoMVC</name> <name>TodoMVC</name>
...@@ -17,25 +18,32 @@ ...@@ -17,25 +18,32 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<resourcesDir>${project.basedir}/src/main/resources</resourcesDir> <resourcesDir>${project.basedir}/src/main/resources</resourcesDir>
<staticapps.version>0.8.5</staticapps.version> <staticapps.version>0.9.11</staticapps.version>
<merge.version>0.5.2</merge.version> <merge.version>0.6.0</merge.version>
<duel.version>0.8.2</duel.version> <duel.version>0.9.7</duel.version>
<slf4j.version>1.6.4</slf4j.version> <slf4j.version>1.7.12</slf4j.version>
<javac.version>1.6</javac.version> <javac.version>1.7</javac.version>
<duel.clientPrefix>todos.views</duel.clientPrefix> <duel.clientPrefix>todos.views</duel.clientPrefix>
<duel.serverPrefix>com.example.todos.views</duel.serverPrefix> <duel.serverPrefix>com.todomvc.duel.views</duel.serverPrefix>
<duel.sourceDir>${resourcesDir}/views/</duel.sourceDir> <duel.sourceDir>${resourcesDir}/views/</duel.sourceDir>
<duel.clientPath>/js/</duel.clientPath> <duel.clientPath>/js/</duel.clientPath>
<merge.cdnMapFile>/cdn.properties</merge.cdnMapFile> <merge.cdnMapFile>/cdn.properties</merge.cdnMapFile>
<merge.cdnRoot>/cdn/</merge.cdnRoot> <merge.cdnRoot>/cdn/</merge.cdnRoot>
<merge.cdnFiles>.ico .png .jpg .gif .eot .woff .ttf .svg .svgz</merge.cdnFiles> <merge.cdnFiles>.ico .png .jpg .gif .cur .eot .woff .ttf .svg .svgz</merge.cdnFiles>
<staticapps.config>${project.basedir}/staticapp.json</staticapps.config> <staticapps.config>${project.basedir}/staticapp.json</staticapps.config>
</properties> </properties>
<dependencies> <dependencies>
<!-- SLF4J runtime -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- DUEL runtime --> <!-- DUEL runtime -->
<dependency> <dependency>
<groupId>org.duelengine</groupId> <groupId>org.duelengine</groupId>
...@@ -49,13 +57,6 @@ ...@@ -49,13 +57,6 @@
<artifactId>duel-staticapps</artifactId> <artifactId>duel-staticapps</artifactId>
<version>${staticapps.version}</version> <version>${staticapps.version}</version>
</dependency> </dependency>
<!-- SLF4J runtime -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
...@@ -75,10 +76,11 @@ ...@@ -75,10 +76,11 @@
<plugin> <plugin>
<groupId>org.apache.tomcat.maven</groupId> <groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId> <artifactId>tomcat7-maven-plugin</artifactId>
<version>2.0-beta-1</version> <version>2.2</version>
<configuration> <configuration>
<path>/</path> <path>/</path>
<port>8080</port> <port>8080</port>
<uriEncoding>UTF-8</uriEncoding>
<warSourceDirectory>${project.build.directory}/${project.build.finalName}/</warSourceDirectory> <warSourceDirectory>${project.build.directory}/${project.build.finalName}/</warSourceDirectory>
</configuration> </configuration>
</plugin> </plugin>
......
# DUEL TodoMVC Example # DUEL • [TodoMVC](http://todomvc.com)
> DUEL is a dual-side templating engine using HTML for layout and 100% pure JavaScript as the binding language. The same views may be executed both directly in the browser (client-side template) and on the server (server-side template). > DUEL is a dual-side templating engine using HTML for layout and 100% pure JavaScript as the binding language. The same views may be executed both directly in the browser (client-side template) and on the server (server-side template).
> _[DUEL - bitbucket.org/mckamey/duel/wiki/Home](http://bitbucket.org/mckamey/duel/wiki/Home)_
## Resources
## Learning DUEL - [Website](http://duelengine.org)
- [Documentation](http://bitbucket.org/mckamey/duel/wiki/Home)
- [Syntax](https://bitbucket.org/mckamey/duel/wiki/Syntax)
- [Examples](https://bitbucket.org/mckamey/duel/wiki/Examples)
- [Source](https://bitbucket.org/mckamey/duel/src)
The [DUEL website](http://bitbucket.org/mckamey/duel/wiki/Home) is a great resource for getting started. *Let us [know](https://github.com/tastejs/todomvc/issues) if you discover anything worth sharing.*
Here are some links you may find helpful:
* [Syntax](https://bitbucket.org/mckamey/duel/wiki/Syntax)
* [Examples](https://bitbucket.org/mckamey/duel/wiki/Examples)
* [DUEL on BitBucket](https://bitbucket.org/mckamey/duel/src)
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
## Implementation ## Implementation
...@@ -44,4 +40,8 @@ To run a debug-able version using Tomcat 7 as the web server, use this Maven com ...@@ -44,4 +40,8 @@ To run a debug-able version using Tomcat 7 as the web server, use this Maven com
mvn tomcat7:run mvn tomcat7:run
Then navigate your browser to http://localhost:8080/ Then navigate your browser to <http://localhost:8080/>
## Credit
Created by [Stephen McKamey](http://mck.me)
<view name="HomePage"><!doctype html> <view name="HomePage"><!doctype html>
<html lang="en" data-framework="duel"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>DUEL &bull; TodoMVC</title> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>DUEL • TodoMVC</title>
<link rel="stylesheet" href="/css/styles.merge"> <link rel="stylesheet" href="/css/styles.merge">
</head> </head>
<body> <body>
<footer id="info"> <footer class="info">
<p>Double-click to edit a todo</p> <p>Double-click to edit a todo</p>
<p>Ported to <a href="http://duelengine.org">DUEL</a> by <a href="http://mck.me">Stephen McKamey</a></p> <p>Ported to <a href="http://duelengine.org">DUEL</a> by <a href="http://mck.me">Stephen McKamey</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
......
<view name="Stats"> <view name="Stats">
<%-- this footer should hidden by default and shown when there are todos --%> <%-- This footer should hidden by default and shown when there are todos. --%>
<footer id="footer" if="<%= data.total %>"> <footer class="footer" if="<%= data.total %>">
<span id="todo-count"><strong><%= data.active %></strong> <%= (data.active === 1) ? 'item' : 'items' %> left</span> <span class="todo-count"><strong><%= data.active %></strong> <%= (data.active === 1) ? 'item' : 'items' %> left</span>
<%-- TODO: implement routing <ul class="filters">
<ul id="filters">
<li> <li>
<a class="selected" href="#/">All</a> <a href="#/" class="<%= !data.filter ? 'selected' : null %>">All</a>
</li> </li>
<li> <li>
<a href="#/active">Active</a> <a href="#/active" class="<%= data.filter === 'active' ? 'selected' : null %>">Active</a>
</li> </li>
<li> <li>
<a href="#/completed">Completed</a> <a href="#/completed" class="<%= data.filter === 'completed' ? 'selected' : null %>">Completed</a>
</li> </li>
</ul> </ul>
--%>
<button id="clear-completed" <%-- Hidden if no completed items are left ↓ --%>
<button class="clear-completed"
if="<%= data.completed %>" if="<%= data.completed %>"
onclick="<%= todos.actions.clear_click %>">Clear completed</button> onclick="<%= todos.actions.clearOnClick %>">Clear completed</button>
</footer> </footer>
<view name="Task"> <view name="Task">
<%-- could have embedded in 'tasks' for-loop, but this allows us to add single tasks --%> <%-- List items should get the class `editing` when editing and `completed` when marked as completed. --%>
<li class="<%= data.completed ? 'completed' : '' %>"> <li class="<%= data.completed ? 'completed' : '' %>">
<div class="view"> <div class="view">
<input class="toggle" type="checkbox" checked="<%= data.completed %>" <input class="toggle" type="checkbox" checked="<%= data.completed %>"
onchange="<%= todos.actions.completed_change(data.id) %>"> onchange="<%= todos.actions.completedOnChange(data.id) %>">
<label ondblclick="<%= todos.actions.editOnDblclick %>"><%= data.title %></label>
<label ondblclick="<%= todos.actions.content_dblclick(data.id) %>"><%= data.title %></label> <button class="destroy" onclick="<%= todos.actions.removeOnClick(data.id) %>"></button>
<button class="destroy" onclick="<%= todos.actions.remove_click(data.id) %>"></button>
</div> </div>
<input class="edit" type="text" value="<%= data.title %>" <input class="edit" type="text" value="<%= data.title %>"
onblur="<%= todos.actions.edit_blur(data.id) %>" onblur="<%= todos.actions.editOnBlur(data.id) %>"
onkeypress="<%= todos.actions.edit_keypress(data.id) %>"> onkeydown="<%= todos.actions.editOnKeydown(data.id) %>">
</li> </li>
<view name="Tasks"> <view name="Tasks">
<%-- this section should hidden by default and shown when there are todos --%> <%-- This section should be hidden by default and shown when there are todos. --%>
<section id="main" if="<%= data.tasks.length %>"> <section class="main" if="<%= data.tasks && data.tasks.length %>">
<input class="toggle-all" type="checkbox" checked="<%= !data.stats.active %>"
<input id="toggle-all" type="checkbox" checked="<%= !data.stats.active %>" onchange="<%= todos.actions.toggleOnChange %>">
onchange="<%= todos.actions.toggle_change %>">
<label for="toggle-all">Mark all as complete</label> <label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<ul id="todo-list">
<for each="<%= data.tasks %>"> <for each="<%= data.tasks %>">
<call view="Task"> <call view="Task">
</for> </for>
......
<view name="TodoApp"> <view name="TodoApp">
<section id="todoapp"> <section class="todoapp">
<header id="header"> <header class="header">
<h1>todos</h1> <h1>todos</h1>
<input id="new-todo" <input class="new-todo" placeholder="What needs to be done?" autofocus
placeholder="What needs to be done?" onblur="<%= todos.actions.add_blur %>" onkeydown="<%= todos.actions.addOnKeydown %>">
autofocus
onblur="<%= todos.actions.add_blur %>"
onkeypress="<%= todos.actions.add_keypress %>">
</header> </header>
<call view="Tasks" data="data" /> <call view="Tasks" data="data" />
<call view="Stats" data="data.stats" /> <call view="Stats" data="data.stats" />
</section> </section>
...@@ -21,4 +21,17 @@ ...@@ -21,4 +21,17 @@
<servlet-name>routing-servlet</servlet-name> <servlet-name>routing-servlet</servlet-name>
<url-pattern>/</url-pattern> <url-pattern>/</url-pattern>
</servlet-mapping> </servlet-mapping>
<mime-mapping>
<extension>html</extension>
<mime-type>text/html;charset=utf-8</mime-type>
</mime-mapping>
<mime-mapping>
<extension>css</extension>
<mime-type>text/css;charset=utf-8</mime-type>
</mime-mapping>
<mime-mapping>
<extension>js</extension>
<mime-type>application/javascript;charset=utf-8</mime-type>
</mime-mapping>
</web-app> </web-app>
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}
...@@ -12,111 +12,85 @@ button { ...@@ -12,111 +12,85 @@ button {
font-size: 100%; font-size: 100%;
vertical-align: baseline; vertical-align: baseline;
font-family: inherit; font-family: inherit;
font-weight: inherit;
color: inherit; color: inherit;
-webkit-appearance: none; -webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none; appearance: none;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
} }
body { body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em; line-height: 1.4em;
background: #eaeaea url('bg.png'); background: #f5f5f5;
color: #4d4d4d; color: #4d4d4d;
width: 550px; min-width: 230px;
max-width: 550px;
margin: 0 auto; margin: 0 auto;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased; -moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased; font-smoothing: antialiased;
font-weight: 300;
} }
button, button,
input[type="checkbox"] { input[type="checkbox"] {
outline: none; outline: none;
} }
#todoapp { .hidden {
display: none;
}
.todoapp {
background: #fff; background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0; margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative; position: relative;
border-top-left-radius: 2px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
border-top-right-radius: 2px; 0 25px 50px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
} }
#todoapp:before { .todoapp input::-webkit-input-placeholder {
content: ''; font-style: italic;
border-left: 1px solid #f5d6d6; font-weight: 300;
border-right: 1px solid #f5d6d6; color: #e6e6e6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
} }
#todoapp input::-webkit-input-placeholder { .todoapp input::-moz-placeholder {
font-style: italic; font-style: italic;
font-weight: 300;
color: #e6e6e6;
} }
#todoapp input::-moz-placeholder { .todoapp input::input-placeholder {
font-style: italic; font-style: italic;
color: #a9a9a9; font-weight: 300;
color: #e6e6e6;
} }
#todoapp h1 { .todoapp h1 {
position: absolute; position: absolute;
top: -120px; top: -155px;
width: 100%; width: 100%;
font-size: 70px; font-size: 100px;
font-weight: bold; font-weight: 100;
text-align: center; text-align: center;
color: #b3b3b3; color: rgba(175, 47, 47, 0.15);
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility; -webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility; -moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
#header { .new-todo,
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.edit { .edit {
position: relative; position: relative;
margin: 0; margin: 0;
width: 100%; width: 100%;
font-size: 24px; font-size: 24px;
font-family: inherit; font-family: inherit;
font-weight: inherit;
line-height: 1.4em; line-height: 1.4em;
border: 0; border: 0;
outline: none; outline: none;
...@@ -124,89 +98,83 @@ input[type="checkbox"] { ...@@ -124,89 +98,83 @@ input[type="checkbox"] {
padding: 6px; padding: 6px;
border: 1px solid #999; border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased; -moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased; font-smoothing: antialiased;
} }
#new-todo { .new-todo {
padding: 16px 16px 16px 60px; padding: 16px 16px 16px 60px;
border: none; border: none;
background: rgba(0, 0, 0, 0.02); background: rgba(0, 0, 0, 0.003);
z-index: 2; box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
box-shadow: none;
} }
#main { .main {
position: relative; position: relative;
z-index: 2; z-index: 2;
border-top: 1px dotted #adadad; border-top: 1px solid #e6e6e6;
} }
label[for='toggle-all'] { label[for='toggle-all'] {
display: none; display: none;
} }
#toggle-all { .toggle-all {
position: absolute; position: absolute;
top: -42px; top: -55px;
left: -4px; left: -12px;
width: 40px; width: 60px;
height: 34px;
text-align: center; text-align: center;
/* Mobile Safari */ border: none; /* Mobile Safari */
border: none;
} }
#toggle-all:before { .toggle-all:before {
content: '»'; content: '';
font-size: 28px; font-size: 22px;
color: #d9d9d9; color: #e6e6e6;
padding: 0 25px 7px; padding: 10px 27px 10px 27px;
} }
#toggle-all:checked:before { .toggle-all:checked:before {
color: #737373; color: #737373;
} }
#todo-list { .todo-list {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
#todo-list li { .todo-list li {
position: relative; position: relative;
font-size: 24px; font-size: 24px;
border-bottom: 1px dotted #ccc; border-bottom: 1px solid #ededed;
} }
#todo-list li:last-child { .todo-list li:last-child {
border-bottom: none; border-bottom: none;
} }
#todo-list li.editing { .todo-list li.editing {
border-bottom: none; border-bottom: none;
padding: 0; padding: 0;
} }
#todo-list li.editing .edit { .todo-list li.editing .edit {
display: block; display: block;
width: 506px; width: 506px;
padding: 13px 17px 12px 17px; padding: 13px 17px 12px 17px;
margin: 0 0 0 43px; margin: 0 0 0 43px;
} }
#todo-list li.editing .view { .todo-list li.editing .view {
display: none; display: none;
} }
#todo-list li .toggle { .todo-list li .toggle {
text-align: center; text-align: center;
width: 40px; width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */ /* auto, since non-WebKit browsers doesn't support input styling */
...@@ -215,47 +183,35 @@ label[for='toggle-all'] { ...@@ -215,47 +183,35 @@ label[for='toggle-all'] {
top: 0; top: 0;
bottom: 0; bottom: 0;
margin: auto 0; margin: auto 0;
/* Mobile Safari */ border: none; /* Mobile Safari */
border: none;
-webkit-appearance: none; -webkit-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none; appearance: none;
} }
#todo-list li .toggle:after { .todo-list li .toggle:after {
content: '✔'; content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
/* 40 + a couple of pixels visual adjustment */
line-height: 43px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
} }
#todo-list li .toggle:checked:after { .todo-list li .toggle:checked:after {
color: #85ada7; content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
} }
#todo-list li label { .todo-list li label {
white-space: pre; white-space: pre;
word-break: break-word; word-break: break-word;
padding: 15px 60px 15px 15px; padding: 15px 60px 15px 15px;
margin-left: 45px; margin-left: 45px;
display: block; display: block;
line-height: 1.2; line-height: 1.2;
-webkit-transition: color 0.4s;
transition: color 0.4s; transition: color 0.4s;
} }
#todo-list li.completed label { .todo-list li.completed label {
color: #a9a9a9; color: #d9d9d9;
text-decoration: line-through; text-decoration: line-through;
} }
#todo-list li .destroy { .todo-list li .destroy {
display: none; display: none;
position: absolute; position: absolute;
top: 0; top: 0;
...@@ -264,68 +220,65 @@ label[for='toggle-all'] { ...@@ -264,68 +220,65 @@ label[for='toggle-all'] {
width: 40px; width: 40px;
height: 40px; height: 40px;
margin: auto 0; margin: auto 0;
font-size: 22px; font-size: 30px;
color: #a88a8a; color: #cc9a9a;
-webkit-transition: all 0.2s; margin-bottom: 11px;
transition: all 0.2s; transition: color 0.2s ease-out;
} }
#todo-list li .destroy:hover { .todo-list li .destroy:hover {
text-shadow: 0 0 1px #000, color: #af5b5e;
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
transform: scale(1.3);
} }
#todo-list li .destroy:after { .todo-list li .destroy:after {
content: ''; content: '×';
} }
#todo-list li:hover .destroy { .todo-list li:hover .destroy {
display: block; display: block;
} }
#todo-list li .edit { .todo-list li .edit {
display: none; display: none;
} }
#todo-list li.editing:last-child { .todo-list li.editing:last-child {
margin-bottom: -1px; margin-bottom: -1px;
} }
#footer { .footer {
color: #777; color: #777;
padding: 0 15px; padding: 10px 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px; height: 20px;
z-index: 1;
text-align: center; text-align: center;
border-top: 1px solid #e6e6e6;
} }
#footer:before { .footer:before {
content: ''; content: '';
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 31px; bottom: 0;
left: 0; left: 0;
height: 50px; height: 50px;
z-index: -1; overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 6px 0 -3px rgba(255, 255, 255, 0.8), 0 8px 0 -3px #f6f6f6,
0 7px 1px -3px rgba(0, 0, 0, 0.3), 0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 43px 0 -6px rgba(255, 255, 255, 0.8), 0 16px 0 -6px #f6f6f6,
0 44px 2px -6px rgba(0, 0, 0, 0.2); 0 17px 2px -6px rgba(0, 0, 0, 0.2);
} }
#todo-count { .todo-count {
float: left; float: left;
text-align: left; text-align: left;
} }
#filters { .todo-count strong {
font-weight: 300;
}
.filters {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
...@@ -334,69 +287,79 @@ label[for='toggle-all'] { ...@@ -334,69 +287,79 @@ label[for='toggle-all'] {
left: 0; left: 0;
} }
#filters li { .filters li {
display: inline; display: inline;
} }
#filters li a { .filters li a {
color: #83756f; color: inherit;
margin: 2px; margin: 3px;
padding: 3px 7px;
text-decoration: none; text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
} }
#filters li a.selected { .filters li a.selected,
font-weight: bold; .filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
} }
#clear-completed { .filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right; float: right;
position: relative; position: relative;
line-height: 20px; line-height: 20px;
text-decoration: none; text-decoration: none;
background: rgba(0, 0, 0, 0.1); cursor: pointer;
font-size: 11px; position: relative;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
} }
#clear-completed:hover { .clear-completed:hover {
background: rgba(0, 0, 0, 0.15); text-decoration: underline;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
} }
#info { .info {
margin: 65px auto 0; margin: 65px auto 0;
color: #a6a6a6; color: #bfbfbf;
font-size: 12px; font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center; text-align: center;
} }
#info a { .info p {
line-height: 1;
}
.info a {
color: inherit; color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
} }
/* /*
Hack to remove background from Mobile Safari. Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera Can't use it globally since it destroys checkboxes in Firefox
*/ */
@media screen and (-webkit-min-device-pixel-ratio:0) { @media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all, .toggle-all,
#todo-list li .toggle { .todo-list li .toggle {
background: none; background: none;
} }
#todo-list li .toggle { .todo-list li .toggle {
height: 40px; height: 40px;
} }
#toggle-all { .toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg); -webkit-transform: rotate(90deg);
transform: rotate(90deg); transform: rotate(90deg);
-webkit-appearance: none; -webkit-appearance: none;
...@@ -404,151 +367,12 @@ label[for='toggle-all'] { ...@@ -404,151 +367,12 @@ label[for='toggle-all'] {
} }
} }
.hidden { @media (max-width: 430px) {
display: none; .footer {
} height: 50px;
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #C5C5C5;
border-bottom: 1px dashed #F7F7F7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
-webkit-transition-property: left;
transition-property: left;
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
.learn-bar > .learn {
left: 8px;
} }
.learn-bar #todoapp { .filters {
width: 550px; bottom: 10px;
margin: 130px auto 40px auto;
} }
} }
# todomvc base file # todomvc base files
/bower_components/todomvc-common/base.css /css/base.css
/css/index.css
/*global window */ /*global window */
/** /**
* @license DUEL v0.8.2 http://duelengine.org * @license DUEL v0.9.7 http://duelengine.org
* Copyright (c)2006-2012 Stephen M. McKamey. * Copyright (c)2006-2012 Stephen M. McKamey.
* Licensed under The MIT License. * Licensed under The MIT License.
*/ */
/*jshint smarttabs:true */
/** /**
* @public * @public
...@@ -67,7 +68,7 @@ var duel = ( ...@@ -67,7 +68,7 @@ var duel = (
/** /**
* Wraps a data value to maintain as raw markup in output * Wraps a data value to maintain as raw markup in output
* *
* @private * @private
* @this {Markup} * @this {Markup}
* @param {string} value The value * @param {string} value The value
...@@ -84,7 +85,7 @@ var duel = ( ...@@ -84,7 +85,7 @@ var duel = (
/** /**
* Renders the value * Renders the value
* *
* @public * @public
* @override * @override
* @this {Markup} * @this {Markup}
...@@ -96,7 +97,7 @@ var duel = ( ...@@ -96,7 +97,7 @@ var duel = (
/** /**
* Determines if the value is an Array * Determines if the value is an Array
* *
* @private * @private
* @param {*} val the object being tested * @param {*} val the object being tested
* @return {boolean} * @return {boolean}
...@@ -105,29 +106,9 @@ var duel = ( ...@@ -105,29 +106,9 @@ var duel = (
return (val instanceof Array); return (val instanceof Array);
}; };
/**
* Determines the type of the value
*
* @private
* @param {*} val the object being tested
* @return {number}
*/
function getType(val) {
switch (typeof val) {
case 'object':
return !val ? NUL : (isArray(val) ? ARY : ((val instanceof Markup) ? RAW : ((val instanceof Date) ? VAL : OBJ)));
case 'function':
return FUN;
case 'undefined':
return NUL;
default:
return VAL;
}
}
/** /**
* Determines if the value is a string * Determines if the value is a string
* *
* @private * @private
* @param {*} val the object being tested * @param {*} val the object being tested
* @return {boolean} * @return {boolean}
...@@ -138,7 +119,7 @@ var duel = ( ...@@ -138,7 +119,7 @@ var duel = (
/** /**
* Determines if the value is a function * Determines if the value is a function
* *
* @private * @private
* @param {*} val the object being tested * @param {*} val the object being tested
* @return {boolean} * @return {boolean}
...@@ -148,140 +129,28 @@ var duel = ( ...@@ -148,140 +129,28 @@ var duel = (
} }
/** /**
* String buffer * Determines the type of the value
* *
* @private
* @this {Buffer}
* @constructor
*/
function Buffer() {
/**
* @type {Array|string}
* @private
*/
this.value = Buffer.FAST ? '' : [];
}
/**
* Only IE<9 benefits from Array.join()
*
* @private
* @constant
* @type {boolean}
*/
Buffer.FAST = !(scriptEngine && scriptEngine() < 9);
/**
* Appends to the internal value
*
* @public
* @this {Buffer}
* @param {null|string} v1
* @param {null|string=} v2
* @param {null|string=} v3
*/
Buffer.prototype.append = function(v1, v2, v3) {
if (Buffer.FAST) {
if (v1 !== null) {
this.value += v1;
if (v2 !== null && v2 !== undef) {
this.value += v2;
if (v3 !== null && v3 !== undef) {
this.value += v3;
}
}
}
} else {
this.value.push.apply(
// Closure Compiler type cast
/** @type{Array} */(this.value),
arguments);
}
};
/**
* Clears the internal value
*
* @public
* @this {Buffer}
*/
Buffer.prototype.clear = function() {
this.value = Buffer.FAST ? '' : [];
};
/**
* Renders the value
*
* @public
* @override
* @this {Buffer}
* @return {string} value
*/
Buffer.prototype.toString = function() {
return Buffer.FAST ?
// Closure Compiler type cast
/** @type{string} */(this.value) :
this.value.join('');
};
function digits(n) {
return (n < 10) ? '0'+n : n;
}
/**
* Formats the value as a string
*
* @private * @private
* @param {*} val the object being rendered * @param {*} val the object being tested
* @return {string|null} * @return {number}
*/ */
function asString(val) { function getType(val) {
var buffer, needsDelim; switch (typeof val) {
switch (getType(val)) { case 'object':
case VAL: return !val ? NUL : (isArray(val) ? ARY : ((val instanceof Markup) ? RAW : ((val instanceof Date) ? VAL : OBJ)));
return ''+val; case 'function':
case NUL: return FUN;
return ''; case 'undefined':
case ARY: return NUL;
// flatten into simple list default:
buffer = new Buffer(); return VAL;
for (var i=0, length=val.length; i<length; i++) {
if (needsDelim) {
buffer.append(', ');
} else {
needsDelim = true;
}
buffer.append(asString(val[i]));
}
return buffer.toString();
case OBJ:
// format JSON-like
buffer = new Buffer();
buffer.append('{');
for (var key in val) {
if (val.hasOwnProperty(key)) {
if (needsDelim) {
buffer.append(', ');
} else {
needsDelim = true;
}
buffer.append(key, '=', asString(val[key]));
}
}
buffer.append('}');
return buffer.toString();
} }
// Closure Compiler type cast
return /** @type{string} */(val);
} }
/** /**
* Wraps a binding result with rendering methods * Wraps a binding result with rendering methods
* *
* @private * @private
* @this {Result} * @this {Result}
* @param {Array|Object|string|number} view The result tree * @param {Array|Object|string|number} view The result tree
...@@ -292,7 +161,7 @@ var duel = ( ...@@ -292,7 +161,7 @@ var duel = (
// ensure is rooted element // ensure is rooted element
view = ['', view]; view = ['', view];
} }
/** /**
* @type {Array} * @type {Array}
* @const * @const
...@@ -402,11 +271,20 @@ var duel = ( ...@@ -402,11 +271,20 @@ var duel = (
*/ */
var NAME = 'name'; var NAME = 'name';
/**
* Callback allowed to modify the bound node
*
* @private
* @param {Array} elem bound node
* @return {Array}
*/
var bindFilter;
var bind; var bind;
/** /**
* Appends a node to a parent * Appends a node to a parent
* *
* @private * @private
* @param {Array} parent The parent node * @param {Array} parent The parent node
* @param {Array|Object|string|number} child The child node * @param {Array|Object|string|number} child The child node
...@@ -479,7 +357,7 @@ var duel = ( ...@@ -479,7 +357,7 @@ var duel = (
/** /**
* Binds the child nodes ignoring parent element and attributes * Binds the child nodes ignoring parent element and attributes
* *
* @private * @private
* @param {Array} node The template subtree root * @param {Array} node The template subtree root
* @param {*} data The data item being bound * @param {*} data The data item being bound
...@@ -510,7 +388,7 @@ var duel = ( ...@@ -510,7 +388,7 @@ var duel = (
/** /**
* Binds the content once for each item in data * Binds the content once for each item in data
* *
* @private * @private
* @param {Array|Object|string|number|function(*,number,number):*} node The template subtree root * @param {Array|Object|string|number|function(*,number,number):*} node The template subtree root
* @param {*} data The data item being bound * @param {*} data The data item being bound
...@@ -588,7 +466,7 @@ var duel = ( ...@@ -588,7 +466,7 @@ var duel = (
} }
} }
var type = getType(items); var type = getType(items);
if (type === ARY) { if (type === ARY) {
// iterate over the items // iterate over the items
for (i=0, length=items.length; i<length; i++) { for (i=0, length=items.length; i<length; i++) {
...@@ -607,7 +485,7 @@ var duel = ( ...@@ -607,7 +485,7 @@ var duel = (
/** /**
* Binds the node to the first conditional block that evaluates to true * Binds the node to the first conditional block that evaluates to true
* *
* @private * @private
* @param {Array|Object|string|number|function(*,number,number):Array|Object|string} node The template subtree root * @param {Array|Object|string|number|function(*,number,number):Array|Object|string} node The template subtree root
* @param {*} data The data item being bound * @param {*} data The data item being bound
...@@ -644,7 +522,7 @@ var duel = ( ...@@ -644,7 +522,7 @@ var duel = (
/** /**
* Calls into another view * Calls into another view
* *
* @private * @private
* @param {Array|Object|string|number|function(*,*,*,*):(Object|null)} node The template subtree root * @param {Array|Object|string|number|function(*,*,*,*):(Object|null)} node The template subtree root
* @param {*} data The data item being bound * @param {*} data The data item being bound
...@@ -679,12 +557,12 @@ var duel = ( ...@@ -679,12 +557,12 @@ var duel = (
return (v && isFunction(v.getView)) ? return (v && isFunction(v.getView)) ?
// Closure Compiler type cast // Closure Compiler type cast
bind(v.getView(), d, /** @type {number} */i, /** @type {number} */c, /** @type {string} */k, p) : null; bind(v.getView(), d, (/** @type {number} */i), (/** @type {number} */c), (/** @type {string} */k), p) : null;
} }
/** /**
* Replaces a place holder part with the named part from the calling view * Replaces a place holder part with the named part from the calling view
* *
* @private * @private
* @param {Array|Object|string|number|function(*,*,*,*):(Object|null)} node The template subtree root * @param {Array|Object|string|number|function(*,*,*,*):(Object|null)} node The template subtree root
* @param {*} data The data item being bound * @param {*} data The data item being bound
...@@ -705,7 +583,7 @@ var duel = ( ...@@ -705,7 +583,7 @@ var duel = (
/** /**
* Binds the node to data * Binds the node to data
* *
* @private * @private
* @param {Array|Object|string|number|function(*,*,*,*):(Object|null)} node The template subtree root * @param {Array|Object|string|number|function(*,*,*,*):(Object|null)} node The template subtree root
* @param {*} data The data item being bound * @param {*} data The data item being bound
...@@ -752,7 +630,7 @@ var duel = ( ...@@ -752,7 +630,7 @@ var duel = (
for (var i=1, length=node.length; i<length; i++) { for (var i=1, length=node.length; i<length; i++) {
append(elem, bind(node[i], data, index, count, key, parts)); append(elem, bind(node[i], data, index, count, key, parts));
} }
return elem; return bindFilter ? bindFilter(elem) : elem;
case OBJ: case OBJ:
// attribute map // attribute map
...@@ -770,22 +648,79 @@ var duel = ( ...@@ -770,22 +648,79 @@ var duel = (
return node; return node;
}; };
/**
* Boolean attribute map (used by both render.js & dom.js)
*
* @private
* @constant
* @type {Object.<number>}
*/
var ATTR_BOOL = {
'allowfullscreen': 1,
'async': 1,
'autofocus': 1,
'autoplay': 1,
'checked': 1,
'compact': 1,
'controls': 1,
'declare': 1,
'default': 1,
'defaultchecked': 1,
'defaultmuted': 1,
'defaultselected': 1,
'defer': 1,
'disabled': 1,
'draggable': 1,
'enabled': 1,
'formnovalidate': 1,
'hidden': 1,
'indeterminate': 1,
'inert': 1,
'ismap': 1,
'itemscope': 1,
'loop': 1,
'multiple': 1,
'muted': 1,
'nohref': 1,
'noresize': 1,
'noshade': 1,
'novalidate': 1,
'nowrap': 1,
'open': 1,
'pauseonexit': 1,
'readonly': 1,
'required': 1,
'reversed': 1,
'scoped': 1,
'seamless': 1,
'selected': 1,
'sortable': 1,
'spellcheck': 1,
'translate': 1,
'truespeed': 1,
'typemustmatch': 1,
'visible': 1
// update non-readonly attributes as spec changes
// curl -s "http://www.w3.org/TR/html51/single-page.html" | grep "attribute boolean" > boolean.txt
};
/* factory.js --------------------*/ /* factory.js --------------------*/
/** /**
* Renders an error as text * Renders an error directly as text
* *
* @private * @private
* @param {Error} ex The exception * @param {Error} ex The exception
* @return {string} * @return {string|Result}
*/ */
function onError(ex) { var onError = function(ex) {
return '['+ex+']'; return '[ '+ex+' ]';
} };
/** /**
* Wraps a view definition with binding method * Wraps a view definition with binding method
* *
* @private * @private
* @param {Array|Object|string|number} view The template definition * @param {Array|Object|string|number} view The template definition
* @return {function(*)} * @return {function(*)}
...@@ -798,7 +733,7 @@ var duel = ( ...@@ -798,7 +733,7 @@ var duel = (
/** /**
* Binds and wraps the result * Binds and wraps the result
* *
* @public * @public
* @param {*} data The data item being bound * @param {*} data The data item being bound
* @param {number} index The index of the current data item * @param {number} index The index of the current data item
...@@ -816,15 +751,24 @@ var duel = ( ...@@ -816,15 +751,24 @@ var duel = (
isFinite(count) ? count : 1, isFinite(count) ? count : 1,
isString(key) ? key : null); isString(key) ? key : null);
return new Result(result); return new Result(result);
} catch (ex) { } catch (ex) {
// handle error with context // handle error with context
return new Result(onError(ex)); var errValue = onError(ex);
if (errValue instanceof Result) {
return errValue;
} else {
// render the error as a text node
return new Result(''+errValue);
}
} }
}; };
/** /**
* Gets the internal view definition * Gets the internal view definition
* *
* @private * @private
* @return {Array} * @return {Array}
*/ */
...@@ -845,6 +789,26 @@ var duel = ( ...@@ -845,6 +789,26 @@ var duel = (
return (isFunction(view) && isFunction(view.getView)) ? view : factory(view); return (isFunction(view) && isFunction(view.getView)) ? view : factory(view);
}; };
/**
* @public
* @param {string} value error callback
*/
duel.onerror = function(value) {
if (isFunction(value)) {
onError = value;
}
};
/**
* @public
* @param {string} value onbind filter callback
*/
duel.onbind = function(value) {
if (isFunction(value)) {
bindFilter = value;
}
};
/** /**
* @public * @public
* @param {string} value Markup text * @param {string} value Markup text
...@@ -858,115 +822,174 @@ var duel = ( ...@@ -858,115 +822,174 @@ var duel = (
/** /**
* Void tag lookup * Void tag lookup
* *
* @private * @private
* @constant * @constant
* @type {Object.<boolean>} * @type {Object.<number>}
*/ */
var VOID_TAGS = { var VOID_TAGS = {
'area' : true, 'area': 1,
'base' : true, 'base': 1,
'basefont' : true, 'basefont': 1,
'br' : true, 'br': 1,
'col' : true, 'col': 1,
'frame' : true, 'frame': 1,
'hr' : true, 'embed': 1,
'img' : true, 'hr': 1,
'input' : true, 'img': 1,
'isindex' : true, 'input': 1,
'keygen' : true, 'isindex': 1,
'link' : true, 'keygen': 1,
'meta' : true, 'link': 1,
'param' : true, 'menuitem': 1,
'source' : true, 'meta': 1,
'wbr' : true 'param': 1,
'source': 1,
'track': 1,
'wbr': 1
// update elements as spec changes
// http://www.w3.org/TR/html51/single-page.html#void-elements
}; };
/** /**
* Boolean attribute map * String buffer
* *
* @private
* @this {Buffer}
* @constructor
*/
function Buffer() {
/**
* @type {Array|string}
* @private
*/
this.value = Buffer.FAST ? '' : [];
}
/**
* IE<9 benefits from Array.join() for large strings
*
* @private * @private
* @constant * @constant
* @type {Object.<number>} * @type {boolean}
*/ */
var ATTR_BOOL = { Buffer.FAST = !(scriptEngine && scriptEngine() < 9);
'async': 1,
'checked': 1, /**
'defer': 1, * Appends to the internal value
'disabled': 1, *
'hidden': 1, * @public
'novalidate': 1, * @this {Buffer}
'formnovalidate': 1 * @param {string} a
// can add more attributes here as needed * @param {string=} b
* @param {string=} c
*/
Buffer.prototype.append = function(a, b, c) {
var args = arguments;
if (Buffer.FAST) {
var len = args.length;
if (len > 1) {
if (len > 2) {
b += c;
}
a += b;
}
this.value += a;
} else {
this.value.push.apply(
// Closure Compiler type cast
/** @type{Array} */(this.value),
args);
}
}; };
/**
* Renders the value
*
* @public
* @override
* @this {Buffer}
* @return {string} value
*/
Buffer.prototype.toString = function() {
return Buffer.FAST ?
// Closure Compiler type cast
/** @type{string} */(this.value) :
this.value.join('');
};
// /**
// * Resets the internal value
// *
// * @public
// * @this {Buffer}
// */
// Buffer.prototype.clear = function() {
// this.value = Buffer.FAST ? '' : [];
// };
/** /**
* Encodes invalid literal characters in strings * Encodes invalid literal characters in strings
* *
* @private * @private
* @param {Array|Object|string|number} val The value * @param {Array|Object|string|number} val The value
* @return {Array|Object|string|number} * @return {string}
*/ */
function htmlEncode(val) { function htmlEncode(val) {
if (!isString(val)) { if (!isString(val)) {
return val; return (val !== null && val !== undef) ? ''+val : '';
} }
return val.replace(/[&<>]/g, var map = {
function(ch) { '&': '&amp;',
switch(ch) { '<': '&lt;',
case '&': '>': '&gt;'
return '&amp;'; };
case '<':
return '&lt;'; return val.replace(/[&<>]/g, function(ch) {
case '>': return map[ch] || ch;
return '&gt;'; });
default:
return ch;
}
});
} }
/** /**
* Encodes invalid attribute characters in strings * Encodes invalid attribute characters in strings
* *
* @private * @private
* @param {Array|Object|string|number} val The value * @param {Array|Object|string|number} val The value
* @return {Array|Object|string|number} * @return {string}
*/ */
function attrEncode(val) { function attrEncode(val) {
if (!isString(val)) { if (!isString(val)) {
return val; return (val !== null && val !== undef) ? ''+val : '';
} }
return val.replace(/[&<>"]/g, var map = {
function(ch) { '&': '&amp;',
switch(ch) { '<': '&lt;',
case '&': '>': '&gt;',
return '&amp;'; '"': '&quot;'
case '<': };
return '&lt;';
case '>': return val.replace(/[&<>"]/g, function(ch) {
return '&gt;'; return map[ch] || ch;
case '"': });
return '&quot;';
default:
return ch;
}
});
} }
/** /**
* Renders the comment as a string * Renders the comment as a string
* *
* @private * @private
* @param {Buffer} buffer The output buffer * @param {Buffer} buffer The output buffer
* @param {Array} node The result tree * @param {Array} node The result tree
*/ */
function renderComment(buffer, node) { function renderComment(buffer, node) {
if (node[0] === '!DOCTYPE') { if (node[0].toLowerCase() === '!doctype') {
// emit doctype // emit doctype
buffer.append('<!DOCTYPE ', node[1], '>'); buffer.append('<!doctype ', node[1], '>');
} else { } else {
// emit HTML comment // emit HTML comment
buffer.append('<!--', node[1], '-->'); buffer.append('<!--', node[1], '-->');
...@@ -975,7 +998,7 @@ var duel = ( ...@@ -975,7 +998,7 @@ var duel = (
/** /**
* Renders the element as a string * Renders the element as a string
* *
* @private * @private
* @param {Buffer} buffer The output buffer * @param {Buffer} buffer The output buffer
* @param {Array} node The result tree * @param {Array} node The result tree
...@@ -1002,20 +1025,23 @@ var duel = ( ...@@ -1002,20 +1025,23 @@ var duel = (
for (var name in child) { for (var name in child) {
if (child.hasOwnProperty(name)) { if (child.hasOwnProperty(name)) {
var val = child[name]; var val = child[name];
if (ATTR_BOOL[name]) { if (ATTR_BOOL[name.toLowerCase()]) {
if (val) { if (val) {
val = name; val = name;
} else { } else {
// falsey boolean attributes must not be present // falsey boolean attributes must not be present
continue; continue;
} }
} }
if (getType(val) === NUL) {
// null/undefined removes attributes
continue;
}
buffer.append(' ', name); buffer.append(' ', name);
if (getType(val) !== NUL) { // Closure Compiler type cast
// Closure Compiler type cast buffer.append('="', attrEncode(val), '"');
buffer.append('="', /** @type{string} */(attrEncode(val)), '"');
}
} }
} }
i++; i++;
...@@ -1031,10 +1057,10 @@ var duel = ( ...@@ -1031,10 +1057,10 @@ var duel = (
child = node[i]; child = node[i];
if (isArray(child)) { if (isArray(child)) {
renderElem(buffer, child); renderElem(buffer, child);
} else { } else {
// encode string literals // encode string literals
// Closure Compiler type cast buffer.append(htmlEncode(child));
buffer.append(/** @type{string} */(htmlEncode(child)));
} }
} }
...@@ -1042,29 +1068,38 @@ var duel = ( ...@@ -1042,29 +1068,38 @@ var duel = (
// emit close tag // emit close tag
buffer.append('</', tag, '>'); buffer.append('</', tag, '>');
} }
return buffer;
} }
/** /**
* Renders the result as a string * Renders the result as a string
* *
* @private * @private
* @param {Array} view The compiled view * @param {Array} view The compiled view
* @return {string} * @return {string}
*/ */
function render(view) { function render(view) {
try { try {
var buffer = new Buffer(); return renderElem(new Buffer(), view).toString();
renderElem(buffer, view);
return buffer.toString();
} catch (ex) { } catch (ex) {
// handle error with context // handle error with context
return onError(ex); var errValue = onError(ex);
if (errValue instanceof Result) {
return render(errValue.value);
} else {
// render the error as a string
return (''+errValue);
}
} }
} }
/** /**
* Returns result as HTML text * Returns result as HTML text
* *
* @public * @public
* @override * @override
* @this {Result} * @this {Result}
...@@ -1076,7 +1111,7 @@ var duel = ( ...@@ -1076,7 +1111,7 @@ var duel = (
/** /**
* Immediately writes the resulting value to the document * Immediately writes the resulting value to the document
* *
* @public * @public
* @this {Result} * @this {Result}
* @param {Document} doc optional Document reference * @param {Document} doc optional Document reference
...@@ -1105,43 +1140,80 @@ var duel = ( ...@@ -1105,43 +1140,80 @@ var duel = (
/** /**
* Attribute name map * Attribute name map
* *
* @private * @private
* @constant * @constant
* @type {Object.<string>} * @type {Object.<string>}
*/ */
var ATTR_MAP = { var ATTR_MAP = {
'rowspan': 'rowSpan', 'allowfullscreen': 'allowFullscreen',
'colspan': 'colSpan', 'accesskey': 'accessKey',
'bgcolor': 'bgColor',
'cellpadding': 'cellPadding', 'cellpadding': 'cellPadding',
'cellspacing': 'cellSpacing', 'cellspacing': 'cellSpacing',
'tabindex': 'tabIndex', 'checked': 'defaultChecked',
'accesskey': 'accessKey', 'class': 'className',
'colspan': 'colSpan',
'contenteditable': 'contentEditable',
'defaultchecked': 'defaultChecked',
'defaultselected': 'defaultSelected',
'defaultmuted': 'defaultMuted',
'for': 'htmlFor',
'formnovalidate': 'formNoValidate',
'hidefocus': 'hideFocus', 'hidefocus': 'hideFocus',
'usemap': 'useMap', 'ismap': 'isMap',
'itemscope': 'itemScope',
'maxlength': 'maxLength', 'maxlength': 'maxLength',
'muted': 'defaultMuted',
'nohref': 'noHref',
'noresize': 'noResize',
'noshade': 'noShade',
'novalidate': 'noValidate',
'nowrap': 'noWrap',
'pauseonexit': 'pauseOnExit',
'readonly': 'readOnly', 'readonly': 'readOnly',
'contenteditable': 'contentEditable' 'rowspan': 'rowSpan',
'selected': 'defaultSelected',
'spellcheck': 'spellCheck',
'tabindex': 'tabIndex',
'truespeed': 'trueSpeed',
'typemustmatch': 'typeMustMatch',
'usemap': 'useMap',
'willvalidate': 'willValidate'
// can add more attributes here as needed // can add more attributes here as needed
}; };
/** /**
* Attribute duplicates map * Attribute duplicates map
* *
* @private * @private
* @constant * @constant
* @type {Object.<string>} * @type {Object.<string>}
*/ */
var ATTR_DUP = { var ATTR_DUP = {
'enctype': 'encoding', 'enctype': 'encoding',
'onscroll': 'DOMMouseScroll', 'onscroll': 'DOMMouseScroll'
'checked': 'defaultChecked' // can add more attributes here as needed
};
/**
* Attributes to be set via DOM
*
* @private
* @constant
* @type {Object.<number>}
*/
var ATTR_DOM = {
'autocapitalize': 1,
'autocomplete': 1,
'autocorrect': 1,
'type': 1
// can add more attributes here as needed // can add more attributes here as needed
}; };
/** /**
* Leading SGML line ending pattern * Leading SGML line ending pattern
* *
* @private * @private
* @constant * @constant
* @type {RegExp} * @type {RegExp}
...@@ -1150,7 +1222,7 @@ var duel = ( ...@@ -1150,7 +1222,7 @@ var duel = (
/** /**
* Trailing SGML line ending pattern * Trailing SGML line ending pattern
* *
* @private * @private
* @constant * @constant
* @type {RegExp} * @type {RegExp}
...@@ -1158,8 +1230,8 @@ var duel = ( ...@@ -1158,8 +1230,8 @@ var duel = (
var TRAILING = /[\r\n]+$/; var TRAILING = /[\r\n]+$/;
/** /**
* Creates a DOM element * Creates a DOM element
* *
* @private * @private
* @param {string} tag The element's tag name * @param {string} tag The element's tag name
* @return {Node} * @return {Node}
...@@ -1187,7 +1259,7 @@ var duel = ( ...@@ -1187,7 +1259,7 @@ var duel = (
/** /**
* Appends a child to an element * Appends a child to an element
* *
* @private * @private
* @param {Node} elem The parent element * @param {Node} elem The parent element
* @param {Node} child The child * @param {Node} child The child
...@@ -1248,36 +1320,58 @@ var duel = ( ...@@ -1248,36 +1320,58 @@ var duel = (
/** /**
* Adds an event handler to an element * Adds an event handler to an element
* *
* @private * @private
* @param {Node} elem The element * @param {Node} elem The element
* @param {string} name The event name * @param {string} name The event name
* @param {function(Event)} handler The event handler * @param {function(Event)} handler The event handler
*/ */
function addHandler(elem, name, handler) { function addHandler(elem, name, handler) {
if (name.substr(0,2) === 'on') {
name = name.substr(2);
}
switch (typeof handler) { switch (typeof handler) {
case 'function': case 'function':
if (elem.addEventListener) { if (elem.addEventListener) {
// DOM Level 2 // DOM Level 2
elem.addEventListener((name.substr(0,2) === 'on') ? name.substr(2) : name, handler, false); elem.addEventListener(name, handler, false);
} else if (isFunction(window.jQuery) && getType(elem[name]) !== NUL) {
// cop out and patch IE6-8 with jQuery
var $elem = window.jQuery(elem);
if (isFunction($elem.on)) {
$elem.on(name, handler); // v1.7+
} else {
$elem.bind(name, handler); // pre-1.7
}
} else if (elem.attachEvent && getType(elem[name]) !== NUL) {
// IE legacy events
elem.attachEvent('on'+name, handler);
} else { } else {
// DOM Level 0 // DOM Level 0
elem[name] = handler; var old = elem['on'+name] || elem[name];
elem['on'+name] = elem[name] = !isFunction(old) ? handler :
function(e) {
return (old.call(this, e) !== false) && (handler.call(this, e) !== false);
};
} }
break; break;
case 'string': case 'string':
// inline functions are DOM Level 0 // inline functions are DOM Level 0
/*jslint evil:true */ /*jslint evil:true */
elem[name] = new Function('event', handler); elem['on'+name] = new Function('event', handler);
/*jslint evil:false */ /*jslint evil:false */
break; break;
} }
} }
/** /**
* Appends a child to an element * Appends an attribute to an element
* *
* @private * @private
* @param {Node} elem The element * @param {Node} elem The element
* @param {Object} attr Attributes object * @param {Object} attr Attributes object
...@@ -1304,52 +1398,70 @@ var duel = ( ...@@ -1304,52 +1398,70 @@ var duel = (
if (name) { if (name) {
if (type === NUL) { if (type === NUL) {
value = ''; // null/undefined removes attributes
type = VAL; continue;
} }
name = ATTR_MAP[name.toLowerCase()] || name; name = ATTR_MAP[name.toLowerCase()] || name;
if (ATTR_BOOL[name]) {
elem[name] = !!value;
// also set duplicated attributes if (name === 'style') {
if (ATTR_DUP[name]) { if (getType(elem.style.cssText) !== NUL) {
elem[ATTR_DUP[name]] = !!value;
}
} else if (name === 'style') {
if (typeof elem.style.cssText !== 'undefined') {
elem.style.cssText = value; elem.style.cssText = value;
} else { } else {
elem.style = value; elem.style = value;
} }
} else if (name === 'class') {
elem.className = value;
} else if (name.substr(0,2) === 'on') { } else if (name.substr(0,2) === 'on') {
addHandler(elem, name, value); addHandler(elem, name, value);
// also set duplicated events // also set duplicated events
if (ATTR_DUP[name]) { name = ATTR_DUP[name];
addHandler(elem, ATTR_DUP[name], value); if (name) {
addHandler(elem, name, value);
} }
} else if (type === VAL && name.charAt(0) !== '$') { } else if (!ATTR_DOM[name.toLowerCase()] && (type !== VAL || name.charAt(0) === '$' || getType(elem[name]) !== NUL || getType(elem[ATTR_DUP[name]]) !== NUL)) {
elem.setAttribute(name, value); // direct setting of existing properties
try {
elem[name] = value;
// also set duplicated attributes // also set duplicated properties
if (ATTR_DUP[name]) { name = ATTR_DUP[name];
elem.setAttribute(ATTR_DUP[name], value); if (name) {
elem[name] = value;
}
} catch(ex2) {
if (name.toLowerCase() === 'type' && elem.tagName.toLowerCase() === 'input') {
// IE9 doesn't like HTML5 input types
continue;
}
throw new Error('DOM property '+elem.tagName+'.'+name+': '+ex2);
}
} else if (ATTR_BOOL[name.toLowerCase()]) {
if (value) {
// boolean attributes
elem.setAttribute(name, name);
// also set duplicated attributes
name = ATTR_DUP[name];
if (name) {
elem.setAttribute(name, name);
}
} }
} else { } else {
// allow direct setting of complex properties // http://www.quirksmode.org/dom/w3c_core.html#attributes
elem[name] = value;
// custom and 'data-*' attributes
elem.setAttribute(name, value);
// also set duplicated attributes // also set duplicated attributes
if (ATTR_DUP[name]) { name = ATTR_DUP[name];
elem[ATTR_DUP[name]] = value; if (name) {
elem.setAttribute(name, value);
} }
} }
} }
...@@ -1360,7 +1472,7 @@ var duel = ( ...@@ -1360,7 +1472,7 @@ var duel = (
/** /**
* Tests a node for whitespace * Tests a node for whitespace
* *
* @private * @private
* @param {Node} node The node * @param {Node} node The node
* @return {boolean} * @return {boolean}
...@@ -1371,7 +1483,7 @@ var duel = ( ...@@ -1371,7 +1483,7 @@ var duel = (
/** /**
* Trims whitespace pattern from the text node * Trims whitespace pattern from the text node
* *
* @private * @private
* @param {Node} node The node * @param {Node} node The node
*/ */
...@@ -1383,7 +1495,7 @@ var duel = ( ...@@ -1383,7 +1495,7 @@ var duel = (
/** /**
* Removes leading and trailing whitespace nodes * Removes leading and trailing whitespace nodes
* *
* @private * @private
* @param {Node} elem The node * @param {Node} elem The node
*/ */
...@@ -1406,7 +1518,7 @@ var duel = ( ...@@ -1406,7 +1518,7 @@ var duel = (
/** /**
* Converts the markup to DOM nodes * Converts the markup to DOM nodes
* *
* @private * @private
* @param {string|Markup} value The node * @param {string|Markup} value The node
* @return {Node} * @return {Node}
...@@ -1414,7 +1526,7 @@ var duel = ( ...@@ -1414,7 +1526,7 @@ var duel = (
function toDOM(value) { function toDOM(value) {
var wrapper = createElement('div'); var wrapper = createElement('div');
wrapper.innerHTML = ''+value; wrapper.innerHTML = ''+value;
// trim extraneous whitespace // trim extraneous whitespace
trimWhitespace(wrapper); trimWhitespace(wrapper);
...@@ -1433,7 +1545,7 @@ var duel = ( ...@@ -1433,7 +1545,7 @@ var duel = (
/** /**
* Retrieve and remove method * Retrieve and remove method
* *
* @private * @private
* @param {Node} elem The element * @param {Node} elem The element
* @param {string} key The callback name * @param {string} key The callback name
...@@ -1445,8 +1557,11 @@ var duel = ( ...@@ -1445,8 +1557,11 @@ var duel = (
try { try {
delete elem[key]; delete elem[key];
} catch (ex) { } catch (ex) {
// sometimes IE doesn't like deleting from DOM try {
elem[key] = undef; // IE7 doesn't like deleting from DOM
elem[key] = '';
elem.removeAttribute(key);
} catch (ex2) {}
} }
if (!isFunction(method)) { if (!isFunction(method)) {
...@@ -1454,7 +1569,7 @@ var duel = ( ...@@ -1454,7 +1569,7 @@ var duel = (
/*jslint evil:true */ /*jslint evil:true */
method = new Function(''+method); method = new Function(''+method);
/*jslint evil:false */ /*jslint evil:false */
} catch (ex2) { } catch (ex3) {
// filter // filter
method = null; method = null;
} }
...@@ -1466,7 +1581,7 @@ var duel = ( ...@@ -1466,7 +1581,7 @@ var duel = (
/** /**
* Executes oninit/onload callbacks * Executes oninit/onload callbacks
* *
* @private * @private
* @param {Node} elem The element * @param {Node} elem The element
*/ */
...@@ -1498,7 +1613,7 @@ var duel = ( ...@@ -1498,7 +1613,7 @@ var duel = (
/** /**
* Applies node to DOM * Applies node to DOM
* *
* @private * @private
* @param {Node} elem The element to append * @param {Node} elem The element to append
* @param {Array} node The node to populate * @param {Array} node The node to populate
...@@ -1559,20 +1674,9 @@ var duel = ( ...@@ -1559,20 +1674,9 @@ var duel = (
return elem; return elem;
} }
/**
* Renders an error as a text node
*
* @private
* @param {Error} ex The exception
* @return {Node}
*/
function onErrorDOM(ex) {
return document.createTextNode(onError(ex));
}
/** /**
* Returns result as DOM objects * Returns result as DOM objects
* *
* @public * @public
* @this {Result} * @this {Result}
* @param {Node|string=} elem An optional element or element ID to be replaced or merged * @param {Node|string=} elem An optional element or element ID to be replaced or merged
...@@ -1582,9 +1686,14 @@ var duel = ( ...@@ -1582,9 +1686,14 @@ var duel = (
Result.prototype.toDOM = function(elem, merge) { Result.prototype.toDOM = function(elem, merge) {
// resolve the element ID // resolve the element ID
if (getType(elem) === VAL) { if (getType(elem) === VAL) {
// try as id, then as query selector
elem = document.getElementById( elem = document.getElementById(
// Closure Compiler type cast // Closure Compiler type cast
/** @type{string} */(elem)); /** @type{string} */(elem)) || (('querySelector' in document) ?
document.querySelector(
// Closure Compiler type cast
/** @type{string} */(elem)) :
null);
} }
var view; var view;
...@@ -1598,7 +1707,15 @@ var duel = ( ...@@ -1598,7 +1707,15 @@ var duel = (
} catch (ex) { } catch (ex) {
// handle error with context // handle error with context
view = onErrorDOM(ex); var errValue = onError(ex);
if (errValue instanceof Result) {
return errValue.toDOM(elem || view);
} else {
// render the error as a text node
view = document.createTextNode(''+errValue);
}
} }
if (elem && elem.parentNode) { if (elem && elem.parentNode) {
...@@ -1612,7 +1729,7 @@ var duel = ( ...@@ -1612,7 +1729,7 @@ var duel = (
/** /**
* Replaces entire document with this Result * Replaces entire document with this Result
* *
* @public * @public
* @this {Result} * @this {Result}
*/ */
...@@ -1643,7 +1760,7 @@ var duel = ( ...@@ -1643,7 +1760,7 @@ var duel = (
} catch (ex) { } catch (ex) {
/*jslint evil:true*/ /*jslint evil:true*/
doc = doc.open('text/html'); doc = doc.open('text/html');
doc.write(this.toString()); doc.write(''+this);
doc.close(); doc.close();
/*jslint evil:false*/ /*jslint evil:false*/
} }
......
/* global _ */
(function () { (function () {
'use strict'; 'use strict';
/* jshint ignore:start */
// Underscore's Template Module // Underscore's Template Module
// Courtesy of underscorejs.org // Courtesy of underscorejs.org
var _ = (function (_) { var _ = (function (_) {
...@@ -112,8 +114,14 @@ ...@@ -112,8 +114,14 @@
})({}); })({});
if (location.hostname === 'todomvc.com') { if (location.hostname === 'todomvc.com') {
window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-31081062-1', 'auto');
ga('send', 'pageview');
} }
/* jshint ignore:end */
function redirect() { function redirect() {
if (location.hostname === 'tastejs.github.io') { if (location.hostname === 'tastejs.github.io') {
...@@ -175,13 +183,17 @@ ...@@ -175,13 +183,17 @@
if (learnJSON.backend) { if (learnJSON.backend) {
this.frameworkJSON = learnJSON.backend; this.frameworkJSON = learnJSON.backend;
this.frameworkJSON.issueLabel = framework;
this.append({ this.append({
backend: true backend: true
}); });
} else if (learnJSON[framework]) { } else if (learnJSON[framework]) {
this.frameworkJSON = learnJSON[framework]; this.frameworkJSON = learnJSON[framework];
this.frameworkJSON.issueLabel = framework;
this.append(); this.append();
} }
this.fetchIssueCount();
} }
Learn.prototype.append = function (opts) { Learn.prototype.append = function (opts) {
...@@ -212,6 +224,26 @@ ...@@ -212,6 +224,26 @@
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
}; };
Learn.prototype.fetchIssueCount = function () {
var issueLink = document.getElementById('issue-count-link');
if (issueLink) {
var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
var parsedResponse = JSON.parse(e.target.responseText);
if (parsedResponse instanceof Array) {
var count = parsedResponse.length;
if (count !== 0) {
issueLink.innerHTML = 'This app has ' + count + ' open issues';
document.getElementById('issue-count').style.display = 'inline';
}
}
};
xhr.send();
}
};
redirect(); redirect();
getFile('learn.json', Learn); getFile('learn.json', Learn);
})(); })();
# todomvc base file # todomvc base file
/bower_components/todomvc-common/base.js #/js/lib/todos.js
# libraries # libraries
/js/lib/duel.js /js/lib/duel.js
# model # compiled views
/js/todos/model.js
# views
/js/todos/views/Stats.js /js/todos/views/Stats.js
/js/todos/views/Task.js /js/todos/views/Task.js
/js/todos/views/Tasks.js /js/todos/views/Tasks.js
/js/todos/views/TodoApp.js /js/todos/views/TodoApp.js
# model
/js/todos/model.js
# controller # controller
/js/todos/controller.js /js/todos/controller.js
...@@ -8,52 +8,36 @@ var todos = todos || {}; ...@@ -8,52 +8,36 @@ var todos = todos || {};
/*-- private members -------------------------------*/ /*-- private members -------------------------------*/
var ESC_KEY = 27;
var ENTER_KEY = 13; var ENTER_KEY = 13;
var STATS_ID = 'footer';
var TODOAPP_ID = 'todoapp';
var TASKS_ID = 'main';
var LIST_ID = 'todo-list';
var EDITING_CSS = 'editing';
function getById(id) {
return document.getElementById(id);
}
function refreshStats(stats) { // Poor man's routing.
// get the data addEventListener('hashchange', refreshView, false);
var data = stats || todos.model.stats();
// build the view function curFilter() {
var view = todos.views.Stats(data).toDOM(); return document.location.hash.substr(2);
}
// replace old stats function find(selector, scope) {
var old = getById(STATS_ID); return (scope || document).querySelector(selector);
if (old) {
old.parentNode.replaceChild(view, old);
} else {
getById(TODOAPP_ID).appendChild(view);
}
} }
function refreshAll() { function refreshView() {
// get the data // get the data
var data = { var data = todos.model.viewData(curFilter());
tasks: todos.model.tasks(),
stats: todos.model.stats()
};
// build the view // build the view
var view = todos.views.Tasks(data).toDOM(); var view = todos.views.TodoApp(data).toDOM();
// replace old task list var old = find('.todoapp');
var old = getById(TASKS_ID);
if (old) { if (old) {
// replace old task list
old.parentNode.replaceChild(view, old); old.parentNode.replaceChild(view, old);
} else { } else {
getById(TODOAPP_ID).appendChild(view); // insert at top
document.body.insertBefore(view, document.body.firstChild);
} }
find('.new-todo').focus();
refreshStats(data.stats);
} }
function add(input) { function add(input) {
...@@ -64,16 +48,8 @@ var todos = todos || {}; ...@@ -64,16 +48,8 @@ var todos = todos || {};
return; return;
} }
var task = todos.model.add(title); todos.model.add(title);
refreshView();
var list = getById(LIST_ID);
if (list) {
// add new at the top
list.appendChild(todos.views.Task(task).toDOM());
refreshStats();
} else {
refreshAll();
}
} }
function edit(input, id) { function edit(input, id) {
...@@ -85,99 +61,91 @@ var todos = todos || {}; ...@@ -85,99 +61,91 @@ var todos = todos || {};
} else { } else {
todos.model.remove(id); todos.model.remove(id);
} }
refreshAll(); refreshView();
}
function reset(input, id) {
var task = todos.model.find(id);
if (task) {
input.value = task.title;
}
} }
/*-- export public interface -------------------------------*/ /*-- export public interface -------------------------------*/
// event handlers // event handlers
todos.actions = { todos.actions = {
addBlur: function () { addOnKeydown: function (e) {
add(this);
},
add_keypress: function (e) {
if (e.keyCode === ENTER_KEY) { if (e.keyCode === ENTER_KEY) {
add(this); add(this);
} else if (e.keyCode === ESC_KEY) {
refreshView();
} }
}, },
edit_blur: function (id) { editOnBlur: function (id) {
// create a closure around the ID // create a closure around the ID
return function () { return function () {
edit(this, id); edit(this, id);
}; };
}, },
edit_keypress: function () { editOnKeydown: function (id) {
// create a closure around the ID // create a closure around the ID
return function (e) { return function (e) {
if (e.keyCode === ENTER_KEY) { if (e.keyCode === ENTER_KEY) {
// just blur so doesn't get triggered twice // just blur so doesn't get triggered twice
this.blur(); this.blur();
} else if (e.keyCode === ESC_KEY) {
reset(this, id);
this.blur();
} }
}; };
}, },
remove_click: function (id) { removeOnClick: function (id) {
// create a closure around the ID // create a closure around the ID
return function () { return function () {
todos.model.remove(id); todos.model.remove(id);
refreshAll(); refreshView();
}; };
}, },
clear_click: function () { clearOnClick: function () {
todos.model.expunge(); todos.model.expunge();
refreshAll(); refreshView();
}, },
content_dblclick: function () { editOnDblclick: function () {
// create a closure around the ID var self = this;
var toggleEditingMode = function (li) { while (self.tagName !== 'LI') {
if (li.tagName !== 'LI') { self = self.parentNode;
return toggleEditingMode(li.parentNode); }
}
li.className = EDITING_CSS; self.className = 'editing';
var input = li.getElementsByTagName('input')[1]; var input = find('input[type=text]', self);
if (input) {
input.focus(); input.focus();
input.value = input.value; }
};
return function () {
toggleEditingMode(this);
};
}, },
completed_change: function (id) { completedOnChange: function (id) {
// create a closure around the ID // create a closure around the ID
return function () { return function () {
var checkbox = this; todos.model.toggle(id, this.checked);
todos.model.toggle(id, checkbox.checked); refreshView();
refreshAll();
}; };
}, },
toggle_change: function () { toggleOnChange: function () {
var checkbox = this; todos.model.toggleAll(this.checked);
todos.model.toggleAll(checkbox.checked); refreshView();
refreshAll();
} }
}; };
/*-- init task list -------------------------------*/ /*-- init task list -------------------------------*/
(function (body) { refreshView();
// build out task list
var view = todos.views.TodoApp({
tasks: todos.model.tasks(),
stats: todos.model.stats()
}).toDOM();
// insert at top
body.insertBefore(view, body.firstChild);
})(document.body);
})(todos, window.document); })(todos, window.document);
...@@ -32,6 +32,12 @@ var todos = todos || {}; ...@@ -32,6 +32,12 @@ var todos = todos || {};
}; };
} }
function find(id) {
return tasks.filter(function (task) {
return task.id === id;
})[0];
}
function save() { function save() {
// if doesn't support JSON then will be directly stored in polyfill // if doesn't support JSON then will be directly stored in polyfill
var value = typeof JSON !== 'undefined' ? JSON.stringify(tasks) : tasks; var value = typeof JSON !== 'undefined' ? JSON.stringify(tasks) : tasks;
...@@ -47,25 +53,40 @@ var todos = todos || {}; ...@@ -47,25 +53,40 @@ var todos = todos || {};
tasks = []; tasks = [];
} }
/*-- export public interface -------------------------------*/ function filterTasks(filter) {
if (filter === 'completed' || filter === 'active') {
var completed = filter === 'completed';
return tasks.filter(function (task) {
return task.completed === completed;
});
}
return tasks;
}
todos.model = { function calcStats(filter) {
tasks: function () { var stats = {
return tasks; total: tasks.length,
},
completed: tasks.filter(function (task) {
return task.completed;
}).length,
stats: function () { filter: filter || ''
var stats = {}; };
stats.total = tasks.length; stats.active = stats.total - stats.completed;
stats.completed = tasks.filter(function (task) { return stats;
return task.completed; }
}).length;
stats.active = stats.total - stats.completed; /*-- export public interface -------------------------------*/
return stats; todos.model = {
viewData: function (filter) {
return {
tasks: filterTasks(filter),
stats: calcStats(filter)
};
}, },
add: function (title) { add: function (title) {
...@@ -77,19 +98,17 @@ var todos = todos || {}; ...@@ -77,19 +98,17 @@ var todos = todos || {};
return task; return task;
}, },
find: find,
edit: function (id, title) { edit: function (id, title) {
tasks.filter(function (task) { (find(id) || {}).title = title;
return task.id === id;
})[0].title = title;
save(); save();
}, },
// toggle completion of task // toggle completion of task
toggle: function (id, completed) { toggle: function (id, completed) {
tasks.filter(function (task) { (find(id) || {}).completed = completed;
return task.id === id;
})[0].completed = completed;
save(); save();
}, },
...@@ -104,21 +123,23 @@ var todos = todos || {}; ...@@ -104,21 +123,23 @@ var todos = todos || {};
}, },
remove: function (id) { remove: function (id) {
tasks.forEach(function (task, index) { // traverse in reverse for removals
if (task.id === id) { for (var i = tasks.length - 1; i >= 0; i--) {
tasks.splice(index, 1); if (tasks[i].id === id) {
tasks.splice(i, 1);
} }
}); }
save(); save();
}, },
expunge: function () { expunge: function () {
tasks.forEach(function (task, index) { // traverse in reverse for removals
if (task.completed) { for (var i = tasks.length - 1; i >= 0; i--) {
tasks.splice(index, 1); if (tasks[i].completed) {
tasks.splice(i, 1);
} }
}); }
save(); save();
} }
......
{ {
"targetDir": "www/", "targetDir": "www/",
"sourceDir": "target/todomvc/", "sourceDir": "target/todomvc/",
"serverPrefix" : "com.example.todos.views", "serverPrefix" : "com.todomvc.duel.views",
"cdnMap": "cdn", "cdnMap": "cdn",
"cdnLinksMap": "cdnLinks", "cdnLinksMap": "cdnLinks",
"cdnHost": "./", "cdnHost": "./",
......
/*
DUEL v0.9.7 http://duelengine.org
Copyright (c)2006-2012 Stephen M. McKamey.
Licensed under The MIT License.
*/
var duel=function(m,C,D){function y(a){this.value=a}function l(a){return"function"===typeof a}function k(a){switch(typeof a){case "object":return!a?0:z(a)?2:a instanceof y?5:a instanceof Date?4:3;case "function":return 1;case "undefined":return 0;default:return 4}}function p(a){z(a)||(a=["",a]);this.value=a}function r(a,b){switch(k(b)){case 2:if(""===b[0])for(var d=1,f=b.length;d<f;d++)r(a,b[d]);else a.push(b);break;case 3:if(1===a.length)a.push(b);else if(d=a[1],3===k(d))for(f in b)b.hasOwnProperty(f)&&
(d[f]=b[f]);else a.splice(1,0,b);break;case 4:""!==b&&(b=""+b,d=a.length-1,0<d&&4===k(a[d])?a[d]+=b:a.push(b));break;case 0:break;default:a.push(b)}}function s(a,b,d,f,c,e){var g=3===k(a[1]);if(a.length===(g?3:2))return n(a[a.length-1],b,d,f,c,e);for(var h=[""],g=g?2:1,j=a.length;g<j;g++)r(h,n(a[g],b,d,f,c,e));return h}function E(a,b,d,f,c,e){for(var g=1,h=a.length;g<h;g++){var j=a[g],i=j[1].test;if(3===k(j[1])&&i&&(l(i)&&(i=i(b,d,f,c)),!i))continue;return s(j,b,d,f,c,e)}return null}function q(){this.value=
q.FAST?"":[]}function O(a){if("string"!==typeof a)return null!==a&&a!==D?""+a:"";var b={"&":"&amp;","<":"&lt;",">":"&gt;"};return a.replace(/[&<>]/g,function(a){return b[a]||a})}function P(a){if("string"!==typeof a)return null!==a&&a!==D?""+a:"";var b={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"};return a.replace(/[&<>"]/g,function(a){return b[a]||a})}function F(a,b){var d=b[0]||"",f=b.length,c=1,e,g=Q[d];if("!"===d.charAt(0))"!doctype"===b[0].toLowerCase()?a.append("<!doctype ",b[1],">"):a.append("<\!--",
b[1],"--\>");else{if(d){a.append("<",d);e=b[c];if(3===k(e)){for(var h in e)if(e.hasOwnProperty(h)){var j=e[h];if(G[h.toLowerCase()])if(j)j=h;else continue;0!==k(j)&&(a.append(" ",h),a.append('="',P(j),'"'))}c++}g&&a.append(" /");a.append(">")}for(;c<f;c++)e=b[c],z(e)?F(a,e):a.append(O(e));d&&!g&&a.append("</",d,">");return a}}function H(a){try{return F(new q,a).toString()}catch(b){return a=v(b),a instanceof p?H(a.value):""+a}}function t(a){if(a){if("!"===a.charAt(0))return m.createComment("!"===a?
"":a.substr(1)+" ")}else{if(m.createDocumentFragment)return m.createDocumentFragment();a=""}return"style"===a.toLowerCase()&&m.createStyleSheet?m.createStyleSheet():m.createElement(a)}function w(a,b){if(b){var d=(a.tagName||"").toLowerCase();if(8===a.nodeType)3===b.nodeType&&(a.nodeValue+=b.nodeValue);else if("table"===d&&a.tBodies)if(b.tagName)if((d=b.tagName.toLowerCase())&&"tbody"!==d&&"thead"!==d){var f=0<a.tBodies.length?a.tBodies[a.tBodies.length-1]:null;f||(f=t("th"===d?"thead":"tbody"),a.appendChild(f));
f.appendChild(b)}else!1!==a.canHaveChildren&&a.appendChild(b);else{if(11===b.nodeType)for(;b.firstChild;)w(a,b.removeChild(b.firstChild))}else if("style"===d&&m.createStyleSheet)a.cssText=b;else if(!1!==a.canHaveChildren)a.appendChild(b);else if("object"===d&&b.tagName&&"param"===b.tagName.toLowerCase()){try{a.appendChild(b)}catch(c){}try{a.object&&(a.object[b.name]=b.value)}catch(e){}}}}function I(a,b,d){"on"===b.substr(0,2)&&(b=b.substr(2));switch(typeof d){case "function":if(a.addEventListener)a.addEventListener(b,
d,!1);else if(l(window.jQuery)&&0!==k(a[b]))if(a=window.jQuery(a),l(a.on))a.on(b,d);else a.bind(b,d);else if(a.attachEvent&&0!==k(a[b]))a.attachEvent("on"+b,d);else{var f=a["on"+b]||a[b];a["on"+b]=a[b]=!l(f)?d:function(a){return!1!==f.call(this,a)&&!1!==d.call(this,a)}}break;case "string":a["on"+b]=new Function("event",d)}}function J(a){return!!a&&3===a.nodeType&&(!a.nodeValue||!/\S/.exec(a.nodeValue))}function K(a,b){a&&(3===a.nodeType&&b.exec(a.nodeValue))&&(a.nodeValue=a.nodeValue.replace(b,""))}
function A(a){if(a){for(;J(a.firstChild);)a.removeChild(a.firstChild);for(K(a.firstChild,R);J(a.lastChild);)a.removeChild(a.lastChild);K(a.lastChild,S)}}function L(a,b){var d=a[b];if(d){try{delete a[b]}catch(f){try{a[b]="",a.removeAttribute(b)}catch(c){}}if(!l(d))try{d=new Function(""+d)}catch(e){d=null}}return d}function M(a){if(a){var b=L(a,"$init");b&&b.call(a);(b=L(a,"$load"))?setTimeout(function(){b.call(a);b=a=null},0):b=a=null}}function N(a,b){for(var d=1,f=b.length;d<f;d++){var c=b[d];switch(k(c)){case 2:var e=
c[0],c=N(t(e),c);if("html"===e)return A(c),M(c),c;w(a,c);break;case 4:""!==c&&w(a,m.createTextNode(""+c));break;case 3:if(1===a.nodeType){var e=a,g=c;if(g.name&&m.attachEvent&&!e.parentNode)try{var h=t("<"+e.tagName+' name="'+g.name+'">');e.tagName===h.tagName&&(e=h)}catch(j){}c=void 0;for(c in g)if(g.hasOwnProperty(c)){var i=g[c],l=k(i);if(c&&0!==l)if(c=T[c.toLowerCase()]||c,"style"===c)0!==k(e.style.cssText)?e.style.cssText=i:e.style=i;else if("on"===c.substr(0,2))I(e,c,i),(c=u[c])&&I(e,c,i);else if(!U[c.toLowerCase()]&&
(4!==l||"$"===c.charAt(0)||0!==k(e[c])||0!==k(e[u[c]])))try{e[c]=i,(c=u[c])&&(e[c]=i)}catch(n){if(!("type"===c.toLowerCase()&&"input"===e.tagName.toLowerCase()))throw Error("DOM property "+e.tagName+"."+c+": "+n);}else G[c.toLowerCase()]?i&&(e.setAttribute(c,c),(c=u[c])&&e.setAttribute(c,c)):(e.setAttribute(c,i),(c=u[c])&&e.setAttribute(c,i))}a=e}break;case 5:e=w;g=a;i=c;c=t("div");c.innerHTML=""+i;A(c);if(1===c.childNodes.length)c=c.firstChild;else{for(i=t("");c.firstChild;)i.appendChild(c.firstChild);
c=i}e(g,c)}}A(a);M(a);11===a.nodeType&&1===a.childNodes.length&&(a=a.firstChild);return a}y.prototype.toString=function(){return this.value};var z=Array.isArray||function(a){return a instanceof Array},B,n;n=function(a,b,d,f,c,e){switch(k(a)){case 1:return a(b,d,f,c);case 2:var g=a[0]||"";switch(g){case "$for":a:{var h=a[1]||{},g=[""],j;if(h.hasOwnProperty("count")){j=h.count;l(j)&&(j=j(b,d,f,c));h.hasOwnProperty("data")?(h=h.data,l(h)&&(h=h(b,d,f,c))):h=b;for(b=0;b<j;b++)r(g,s(a,h,b,j,null,e))}else{if(h.hasOwnProperty("in")){var i=
h["in"];l(i)&&(i=i(b,d,f,c));if(3===k(i)){h=[];for(j in i)i.hasOwnProperty(j)&&h.push(j);b=0;for(j=h.length;b<j;b++)r(g,s(a,i[h[b]],b,j,h[b],e));a=g;break a}h=i}else h=h.each,l(h)&&(h=h(b,d,f,c));b=k(h);if(2===b){b=0;for(j=h.length;b<j;b++)r(g,s(a,h[b],b,j,null,e))}else 0!==b&&(g=s(a,h,0,1,null,e))}a=g}return a;case "$xor":return E(a,b,d,f,c,e);case "$if":return E(["$xor",a],b,d,f,c,e);case "$call":e=a[1]||{};if(e.view){g=n(e.view,b,d,f,c);h=e.hasOwnProperty("data")?n(e.data,b,d,f,c):b;j=e.hasOwnProperty("index")?
n(e.index,b,d,f,c):d;i=e.hasOwnProperty("count")?n(e.count,b,d,f,c):f;b=e.hasOwnProperty("key")?n(e.key,b,d,f,c):c;d={};for(f=a.length-1;2<=f;f--)c=a[f],e=c[1]||{},e.hasOwnProperty("name")&&(d[e.name]=c);a=g&&l(g.getView)?n(g.getView(),h,j,i,b,d):null}else a=null;return a;case "$part":return g=(a[1]||{}).name||"",g=e&&e.hasOwnProperty(g)?e[g]:a,s(g,b,d,f,c)}g=[g];h=1;for(j=a.length;h<j;h++)r(g,n(a[h],b,d,f,c,e));return B?B(g):g;case 3:e={};for(g in a)a.hasOwnProperty(g)&&(e[g]=n(a[g],b,d,f,c));return e}return a};
var G={allowfullscreen:1,async:1,autofocus:1,autoplay:1,checked:1,compact:1,controls:1,declare:1,"default":1,defaultchecked:1,defaultmuted:1,defaultselected:1,defer:1,disabled:1,draggable:1,enabled:1,formnovalidate:1,hidden:1,indeterminate:1,inert:1,ismap:1,itemscope:1,loop:1,multiple:1,muted:1,nohref:1,noresize:1,noshade:1,novalidate:1,nowrap:1,open:1,pauseonexit:1,readonly:1,required:1,reversed:1,scoped:1,seamless:1,selected:1,sortable:1,spellcheck:1,translate:1,truespeed:1,typemustmatch:1,visible:1},
v=function(a){return"[ "+a+" ]"},x=function(a){if(!l(a)||!l(a.getView)){var b=a;2!==k(b)&&(b=["",b]);a=function(a,f,c,e){try{var g=n(b,a,isFinite(f)?f:0,isFinite(c)?c:1,"string"===typeof e?e:null);return new p(g)}catch(h){return a=v(h),a instanceof p?a:new p(""+a)}};a.getView=function(){return b}}return a};x.onerror=function(a){l(a)&&(v=a)};x.onbind=function(a){l(a)&&(B=a)};x.raw=function(a){return new y(a)};var Q={area:1,base:1,basefont:1,br:1,col:1,frame:1,embed:1,hr:1,img:1,input:1,isindex:1,keygen:1,
link:1,menuitem:1,meta:1,param:1,source:1,track:1,wbr:1};q.FAST=!(C&&9>C());q.prototype.append=function(a,b,d){var f=arguments;q.FAST?(f=f.length,1<f&&(2<f&&(b+=d),a+=b),this.value+=a):this.value.push.apply(this.value,f)};q.prototype.toString=function(){return q.FAST?this.value:this.value.join("")};p.prototype.toString=function(){return H(this.value)};p.prototype.write=function(a){(a||m).write(""+this)};var T={allowfullscreen:"allowFullscreen",accesskey:"accessKey",bgcolor:"bgColor",cellpadding:"cellPadding",
cellspacing:"cellSpacing",checked:"defaultChecked","class":"className",colspan:"colSpan",contenteditable:"contentEditable",defaultchecked:"defaultChecked",defaultselected:"defaultSelected",defaultmuted:"defaultMuted","for":"htmlFor",formnovalidate:"formNoValidate",hidefocus:"hideFocus",ismap:"isMap",itemscope:"itemScope",maxlength:"maxLength",muted:"defaultMuted",nohref:"noHref",noresize:"noResize",noshade:"noShade",novalidate:"noValidate",nowrap:"noWrap",pauseonexit:"pauseOnExit",readonly:"readOnly",
rowspan:"rowSpan",selected:"defaultSelected",spellcheck:"spellCheck",tabindex:"tabIndex",truespeed:"trueSpeed",typemustmatch:"typeMustMatch",usemap:"useMap",willvalidate:"willValidate"},u={enctype:"encoding",onscroll:"DOMMouseScroll"},U={autocapitalize:1,autocomplete:1,autocorrect:1,type:1},R=/^[\r\n]+/,S=/[\r\n]+$/;p.prototype.toDOM=function(a,b){4===k(a)&&(a=m.getElementById(a)||("querySelector"in m?m.querySelector(a):null));var d;try{b&&(d=a,a=null),d=N(d||t(this.value[0]),this.value)}catch(f){var c=
v(f);if(c instanceof p)return c.toDOM(a||d);d=m.createTextNode(""+c)}a&&a.parentNode&&a.parentNode.replaceChild(d,a);return d};p.prototype.reload=function(){var a=m;try{var b=this.toDOM();a.replaceChild(b,a.documentElement);if(a.createStyleSheet){for(var d=b.firstChild;d&&"HEAD"!==(d.tagName||"");)d=d.nextSibling;for(var f=d&&d.firstChild;f;){if("LINK"===(f.tagName||""))f.href=f.href;f=f.nextSibling}}}catch(c){a=a.open("text/html"),a.write(""+this),a.close()}};return x}(document,window.ScriptEngineMajorVersion);var todos=todos||{};todos.views=todos.views||{};
todos.views.Stats=duel([""," ",["$if",{test:function(a){return a.total}},["footer",{"class":"footer"}," ",["span",{"class":"todo-count"},["strong",function(a){return a.active}]," ",function(a){return 1===a.active?"item":"items"}," left"]," ",["ul",{"class":"filters"}," ",["li"," ",["a",{href:"#/","class":function(a){return!a.filter?"selected":null}},"All"]," "]," ",["li"," ",["a",{href:"#/active","class":function(a){return"active"===a.filter?"selected":null}},"Active"]," "]," ",["li"," ",["a",{href:"#/completed",
"class":function(a){return"completed"===a.filter?"selected":null}},"Completed"]," "]," "]," "," ",["$if",{test:function(a){return a.completed}},["button",{"class":"clear-completed",onclick:function(){return todos.actions.clearOnClick}},"Clear completed"]]]]]);var todos=todos||{};todos.views=todos.views||{};
todos.views.Task=duel([""," ",["li",{"class":function(a){return a.completed?"completed":""}}," ",["div",{"class":"view"}," ",["input",{"class":"toggle",type:"checkbox",checked:function(a){return a.completed},onchange:function(a){return todos.actions.completedOnChange(a.id)}}]," ",["label",{ondblclick:function(){return todos.actions.editOnDblclick}},function(a){return a.title}]," ",["button",{"class":"destroy",onclick:function(a){return todos.actions.removeOnClick(a.id)}}]," "]," ",["input",{"class":"edit",
type:"text",value:function(a){return a.title},onblur:function(a){return todos.actions.editOnBlur(a.id)},onkeydown:function(a){return todos.actions.editOnKeydown(a.id)}}]]]);var todos=todos||{};todos.views=todos.views||{};
todos.views.Tasks=duel([""," ",["$if",{test:function(a){return a.tasks&&a.tasks.length}},["section",{"class":"main"}," ",["input",{"class":"toggle-all",type:"checkbox",checked:function(a){return!a.stats.active},onchange:function(){return todos.actions.toggleOnChange}}]," ",["label",{"for":"toggle-all"},"Mark all as complete"]," ",["ul",{"class":"todo-list"}," ",["$for",{each:function(a){return a.tasks}}," ",["$call",{view:function(){return todos.views.Task}}]]," "]]]]);var todos=todos||{};todos.views=todos.views||{};todos.views.TodoApp=duel(["section",{"class":"todoapp"}," ",["header",{"class":"header"}," ",["h1","todos"]," ",["input",{"class":"new-todo",placeholder:"What needs to be done?",autofocus:!0,onblur:function(){return todos.actions.add_blur},onkeydown:function(){return todos.actions.addOnKeydown}}]," "]," ",["$call",{view:function(){return todos.views.Tasks},data:function(a){return a}}]," ",["$call",{view:function(){return todos.views.Stats},data:function(a){return a.stats}}]]);var todos=todos||{};
(function(j,f,h){function g(a){return c.filter(function(b){return b.id===a})[0]}function d(){var a="undefined"!==typeof JSON?JSON.stringify(c):c;f.setItem(h,a)}var c,e;if(!(e=f)){var i={};e={getItem:function(a){return i[a]},setItem:function(a,b){i[a]=b}}}f=e;c=(e=f.getItem(h))?"undefined"!==typeof JSON?JSON.parse(e):e:[];j.model={viewData:function(a){var b;if("completed"===a||"active"===a){var d="completed"===a;b=c.filter(function(a){return a.completed===d})}else b=c;a={total:c.length,completed:c.filter(function(a){return a.completed}).length,
filter:a||""};a.active=a.total-a.completed;return{tasks:b,stats:a}},add:function(a){a={id:((new Date).getTime()+Math.random()).toString(36),title:a,completed:!1};c.push(a);d();return a},find:g,edit:function(a,b){(g(a)||{}).title=b;d()},toggle:function(a,b){(g(a)||{}).completed=b;d()},toggleAll:function(a){c.forEach(function(b){b.completed=a});d()},remove:function(a){for(var b=c.length-1;0<=b;b--)c[b].id===a&&c.splice(b,1);d()},expunge:function(){for(var a=c.length-1;0<=a;a--)c[a].completed&&c.splice(a,
1);d()}}})(todos,window.localStorage,"todos-duel");var todos=todos||{};
(function(b,e){function d(){var a=b.model.viewData(e.location.hash.substr(2)),a=b.views.TodoApp(a).toDOM(),c=e.querySelector(".todoapp");c?c.parentNode.replaceChild(a,c):e.body.insertBefore(a,e.body.firstChild);e.querySelector(".new-todo").focus()}addEventListener("hashchange",d,!1);b.actions={addOnKeydown:function(a){13===a.keyCode?(a=(this.value||"").trim(),this.value="",a&&(b.model.add(a),d())):27===a.keyCode&&d()},editOnBlur:function(a){return function(){var c=(this.value||"").trim();(this.value=
c)?b.model.edit(a,c):b.model.remove(a);d()}},editOnKeydown:function(a){return function(c){if(13===c.keyCode)this.blur();else if(27===c.keyCode){if(c=b.model.find(a))this.value=c.title;this.blur()}}},removeOnClick:function(a){return function(){b.model.remove(a);d()}},clearOnClick:function(){b.model.expunge();d()},editOnDblclick:function(){for(var a=this;"LI"!==a.tagName;)a=a.parentNode;a.className="editing";(a=(a||e).querySelector("input[type=text]"))&&a.focus()},completedOnChange:function(a){return function(){b.model.toggle(a,
this.checked);d()}},toggleOnChange:function(){b.model.toggleAll(this.checked);d()}};d()})(todos,window.document);
\ No newline at end of file
hr{margin:20px 0;border:0;border-top:1px dashed #c5c5c5;border-bottom:1px dashed #f7f7f7;}.learn a{font-weight:normal;text-decoration:none;color:#b83f45;}.learn a:hover{text-decoration:underline;color:#787e7e;}.learn h3,.learn h4,.learn h5{margin:10px 0;font-weight:500;line-height:1.2;color:#000;}.learn h3{font-size:24px;}.learn h4{font-size:18px;}.learn h5{margin-bottom:0;font-size:14px;}.learn ul{padding:0;margin:0 0 30px 25px;}.learn li{line-height:20px;}.learn p{font-size:15px;font-weight:300;line-height:1.3;margin-top:0;margin-bottom:0;}#issue-count{display:none;}.quote{border:none;margin:20px 0 60px 0;}.quote p{font-style:italic;}.quote p:before{content:'“';font-size:50px;opacity:0.15;position:absolute;top:-20px;left:3px;}.quote p:after{content:'”';font-size:50px;opacity:0.15;position:absolute;bottom:-42px;right:3px;}.quote footer{position:absolute;bottom:-40px;right:0;}.quote footer img{border-radius:3px;}.quote footer a{margin-left:5px;vertical-align:middle;}.speech-bubble{position:relative;padding:10px;background:rgba(0,0,0,0.04);border-radius:5px;}.speech-bubble:after{content:'';position:absolute;top:100%;right:30px;border:13px solid transparent;border-top-color:rgba(0,0,0,0.04);}.learn-bar>.learn{position:absolute;width:272px;top:8px;left:-300px;padding:10px;border-radius:5px;background-color:rgba(255,255,255,0.6);transition-property:left;transition-duration:500ms;}@media (min-width:899px){.learn-bar{width:auto;padding-left:300px;}.learn-bar>.learn{left:8px;}}html,body{margin:0;padding:0;}button{margin:0;padding:0;border:0;background:none;font-size:100%;vertical-align:baseline;font-family:inherit;font-weight:inherit;color:inherit;-webkit-appearance:none;appearance:none;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;font-smoothing:antialiased;}body{font:14px 'Helvetica Neue',Helvetica,Arial,sans-serif;line-height:1.4em;background:#f5f5f5;color:#4d4d4d;min-width:230px;max-width:550px;margin:0 auto;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;font-smoothing:antialiased;font-weight:300;}button,input[type="checkbox"]{outline:none;}.hidden{display:none;}.todoapp{background:#fff;margin:130px 0 40px 0;position:relative;box-shadow:0 2px 4px 0 rgba(0,0,0,0.2),0 25px 50px 0 rgba(0,0,0,0.1);}.todoapp input::-webkit-input-placeholder{font-style:italic;font-weight:300;color:#e6e6e6;}.todoapp input::-moz-placeholder{font-style:italic;font-weight:300;color:#e6e6e6;}.todoapp input::input-placeholder{font-style:italic;font-weight:300;color:#e6e6e6;}.todoapp h1{position:absolute;top:-155px;width:100%;font-size:100px;font-weight:100;text-align:center;color:rgba(175,47,47,0.15);-webkit-text-rendering:optimizeLegibility;-moz-text-rendering:optimizeLegibility;text-rendering:optimizeLegibility;}.new-todo,.edit{position:relative;margin:0;width:100%;font-size:24px;font-family:inherit;font-weight:inherit;line-height:1.4em;border:0;outline:none;color:inherit;padding:6px;border:1px solid #999;box-shadow:inset 0 -1px 5px 0 rgba(0,0,0,0.2);box-sizing:border-box;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;font-smoothing:antialiased;}.new-todo{padding:16px 16px 16px 60px;border:none;background:rgba(0,0,0,0.003);box-shadow:inset 0 -2px 1px rgba(0,0,0,0.03);}.main{position:relative;z-index:2;border-top:1px solid #e6e6e6;}label[for='toggle-all']{display:none;}.toggle-all{position:absolute;top:-55px;left:-12px;width:60px;height:34px;text-align:center;border:none;}.toggle-all:before{content:'❯';font-size:22px;color:#e6e6e6;padding:10px 27px 10px 27px;}.toggle-all:checked:before{color:#737373;}.todo-list{margin:0;padding:0;list-style:none;}.todo-list li{position:relative;font-size:24px;border-bottom:1px solid #ededed;}.todo-list li:last-child{border-bottom:none;}.todo-list li.editing{border-bottom:none;padding:0;}.todo-list li.editing .edit{display:block;width:506px;padding:13px 17px 12px 17px;margin:0 0 0 43px;}.todo-list li.editing .view{display:none;}.todo-list li .toggle{text-align:center;width:40px;height:auto;position:absolute;top:0;bottom:0;margin:auto 0;border:none;-webkit-appearance:none;appearance:none;}.todo-list li .toggle:after{content:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');}.todo-list li .toggle:checked:after{content:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');}.todo-list li label{white-space:pre;word-break:break-word;padding:15px 60px 15px 15px;margin-left:45px;display:block;line-height:1.2;transition:color 0.4s;}.todo-list li.completed label{color:#d9d9d9;text-decoration:line-through;}.todo-list li .destroy{display:none;position:absolute;top:0;right:10px;bottom:0;width:40px;height:40px;margin:auto 0;font-size:30px;color:#cc9a9a;margin-bottom:11px;transition:color 0.2s ease-out;}.todo-list li .destroy:hover{color:#af5b5e;}.todo-list li .destroy:after{content:'×';}.todo-list li:hover .destroy{display:block;}.todo-list li .edit{display:none;}.todo-list li.editing:last-child{margin-bottom:-1px;}.footer{color:#777;padding:10px 15px;height:20px;text-align:center;border-top:1px solid #e6e6e6;}.footer:before{content:'';position:absolute;right:0;bottom:0;left:0;height:50px;overflow:hidden;box-shadow:0 1px 1px rgba(0,0,0,0.2),0 8px 0 -3px #f6f6f6,0 9px 1px -3px rgba(0,0,0,0.2),0 16px 0 -6px #f6f6f6,0 17px 2px -6px rgba(0,0,0,0.2);}.todo-count{float:left;text-align:left;}.todo-count strong{font-weight:300;}.filters{margin:0;padding:0;list-style:none;position:absolute;right:0;left:0;}.filters li{display:inline;}.filters li a{color:inherit;margin:3px;padding:3px 7px;text-decoration:none;border:1px solid transparent;border-radius:3px;}.filters li a.selected,.filters li a:hover{border-color:rgba(175,47,47,0.1);}.filters li a.selected{border-color:rgba(175,47,47,0.2);}.clear-completed,html .clear-completed:active{float:right;position:relative;line-height:20px;text-decoration:none;cursor:pointer;position:relative;}.clear-completed:hover{text-decoration:underline;}.info{margin:65px auto 0;color:#bfbfbf;font-size:10px;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-align:center;}.info p{line-height:1;}.info a{color:inherit;text-decoration:none;font-weight:400;}.info a:hover{text-decoration:underline;}@media screen and (-webkit-min-device-pixel-ratio:0){.toggle-all,.todo-list li .toggle{background:none;}.todo-list li .toggle{height:40px;}.toggle-all{-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-appearance:none;appearance:none;}}@media (max-width:430px){.footer{height:50px;}.filters{bottom:10px;}}
\ No newline at end of file
html,body{margin:0;padding:0;}button{margin:0;padding:0;border:0;background:none;font-size:100%;vertical-align:baseline;font-family:inherit;color:inherit;-webkit-appearance:none;-ms-appearance:none;-o-appearance:none;appearance:none;}body{font:14px 'Helvetica Neue',Helvetica,Arial,sans-serif;line-height:1.4em;background:#eaeaea url("0c975f0bb536597038b63a8cb0d85cff2222b0c9.png");color:#4d4d4d;width:550px;margin:0 auto;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;-ms-font-smoothing:antialiased;-o-font-smoothing:antialiased;font-smoothing:antialiased;}button,input[type="checkbox"]{outline:none;}#todoapp{background:#fff;background:rgba(255,255,255,0.9);margin:130px 0 40px 0;border:1px solid #ccc;position:relative;border-top-left-radius:2px;border-top-right-radius:2px;box-shadow:0 2px 6px 0 rgba(0,0,0,0.2),0 25px 50px 0 rgba(0,0,0,0.15);}#todoapp:before{content:'';border-left:1px solid #f5d6d6;border-right:1px solid #f5d6d6;width:2px;position:absolute;top:0;left:40px;height:100%;}#todoapp input::-webkit-input-placeholder{font-style:italic;}#todoapp input::-moz-placeholder{font-style:italic;color:#a9a9a9;}#todoapp h1{position:absolute;top:-120px;width:100%;font-size:70px;font-weight:bold;text-align:center;color:#b3b3b3;color:rgba(255,255,255,0.3);text-shadow:-1px -1px rgba(0,0,0,0.2);-webkit-text-rendering:optimizeLegibility;-moz-text-rendering:optimizeLegibility;-ms-text-rendering:optimizeLegibility;-o-text-rendering:optimizeLegibility;text-rendering:optimizeLegibility;}#header{padding-top:15px;border-radius:inherit;}#header:before{content:'';position:absolute;top:0;right:0;left:0;height:15px;z-index:2;border-bottom:1px solid #6c615c;background:#8d7d77;background:-webkit-gradient(linear,left top,left bottom,from(rgba(132,110,100,0.8)),to(rgba(101,84,76,0.8)));background:-webkit-linear-gradient(top,rgba(132,110,100,0.8),rgba(101,84,76,0.8));background:linear-gradient(top,rgba(132,110,100,0.8),rgba(101,84,76,0.8));filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83',EndColorStr='#847670');border-top-left-radius:1px;border-top-right-radius:1px;}#new-todo,.edit{position:relative;margin:0;width:100%;font-size:24px;font-family:inherit;line-height:1.4em;border:0;outline:none;color:inherit;padding:6px;border:1px solid #999;box-shadow:inset 0 -1px 5px 0 rgba(0,0,0,0.2);-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;-webkit-font-smoothing:antialiased;-moz-font-smoothing:antialiased;-ms-font-smoothing:antialiased;-o-font-smoothing:antialiased;font-smoothing:antialiased;}#new-todo{padding:16px 16px 16px 60px;border:none;background:rgba(0,0,0,0.02);z-index:2;box-shadow:none;}#main{position:relative;z-index:2;border-top:1px dotted #adadad;}label[for='toggle-all']{display:none;}#toggle-all{position:absolute;top:-42px;left:-4px;width:40px;text-align:center;border:none;}#toggle-all:before{content:'»';font-size:28px;color:#d9d9d9;padding:0 25px 7px;}#toggle-all:checked:before{color:#737373;}#todo-list{margin:0;padding:0;list-style:none;}#todo-list li{position:relative;font-size:24px;border-bottom:1px dotted #ccc;}#todo-list li:last-child{border-bottom:none;}#todo-list li.editing{border-bottom:none;padding:0;}#todo-list li.editing .edit{display:block;width:506px;padding:13px 17px 12px 17px;margin:0 0 0 43px;}#todo-list li.editing .view{display:none;}#todo-list li .toggle{text-align:center;width:40px;height:auto;position:absolute;top:0;bottom:0;margin:auto 0;border:none;-webkit-appearance:none;-ms-appearance:none;-o-appearance:none;appearance:none;}#todo-list li .toggle:after{content:'✔';line-height:43px;font-size:20px;color:#d9d9d9;text-shadow:0 -1px 0 #bfbfbf;}#todo-list li .toggle:checked:after{color:#85ada7;text-shadow:0 1px 0 #669991;bottom:1px;position:relative;}#todo-list li label{white-space:pre;word-break:break-word;padding:15px 60px 15px 15px;margin-left:45px;display:block;line-height:1.2;-webkit-transition:color 0.4s;transition:color 0.4s;}#todo-list li.completed label{color:#a9a9a9;text-decoration:line-through;}#todo-list li .destroy{display:none;position:absolute;top:0;right:10px;bottom:0;width:40px;height:40px;margin:auto 0;font-size:22px;color:#a88a8a;-webkit-transition:all 0.2s;transition:all 0.2s;}#todo-list li .destroy:hover{text-shadow:0 0 1px #000,0 0 10px rgba(199,107,107,0.8);-webkit-transform:scale(1.3);transform:scale(1.3);}#todo-list li .destroy:after{content:'✖';}#todo-list li:hover .destroy{display:block;}#todo-list li .edit{display:none;}#todo-list li.editing:last-child{margin-bottom:-1px;}#footer{color:#777;padding:0 15px;position:absolute;right:0;bottom:-31px;left:0;height:20px;z-index:1;text-align:center;}#footer:before{content:'';position:absolute;right:0;bottom:31px;left:0;height:50px;z-index:-1;box-shadow:0 1px 1px rgba(0,0,0,0.3),0 6px 0 -3px rgba(255,255,255,0.8),0 7px 1px -3px rgba(0,0,0,0.3),0 43px 0 -6px rgba(255,255,255,0.8),0 44px 2px -6px rgba(0,0,0,0.2);}#todo-count{float:left;text-align:left;}#filters{margin:0;padding:0;list-style:none;position:absolute;right:0;left:0;}#filters li{display:inline;}#filters li a{color:#83756f;margin:2px;text-decoration:none;}#filters li a.selected{font-weight:bold;}#clear-completed{float:right;position:relative;line-height:20px;text-decoration:none;background:rgba(0,0,0,0.1);font-size:11px;padding:0 10px;border-radius:3px;box-shadow:0 -1px 0 0 rgba(0,0,0,0.2);}#clear-completed:hover{background:rgba(0,0,0,0.15);box-shadow:0 -1px 0 0 rgba(0,0,0,0.3);}#info{margin:65px auto 0;color:#a6a6a6;font-size:12px;text-shadow:0 1px 0 rgba(255,255,255,0.7);text-align:center;}#info a{color:inherit;}@media screen and (-webkit-min-device-pixel-ratio:0){#toggle-all,#todo-list li .toggle{background:none;}#todo-list li .toggle{height:40px;}#toggle-all{top:-56px;left:-15px;width:65px;height:41px;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-appearance:none;appearance:none;}}.hidden{display:none;}hr{margin:20px 0;border:0;border-top:1px dashed #C5C5C5;border-bottom:1px dashed #F7F7F7;}.learn a{font-weight:normal;text-decoration:none;color:#b83f45;}.learn a:hover{text-decoration:underline;color:#787e7e;}.learn h3,.learn h4,.learn h5{margin:10px 0;font-weight:500;line-height:1.2;color:#000;}.learn h3{font-size:24px;}.learn h4{font-size:18px;}.learn h5{margin-bottom:0;font-size:14px;}.learn ul{padding:0;margin:0 0 30px 25px;}.learn li{line-height:20px;}.learn p{font-size:15px;font-weight:300;line-height:1.3;margin-top:0;margin-bottom:0;}.quote{border:none;margin:20px 0 60px 0;}.quote p{font-style:italic;}.quote p:before{content:'“';font-size:50px;opacity:0.15;position:absolute;top:-20px;left:3px;}.quote p:after{content:'”';font-size:50px;opacity:0.15;position:absolute;bottom:-42px;right:3px;}.quote footer{position:absolute;bottom:-40px;right:0;}.quote footer img{border-radius:3px;}.quote footer a{margin-left:5px;vertical-align:middle;}.speech-bubble{position:relative;padding:10px;background:rgba(0,0,0,0.04);border-radius:5px;}.speech-bubble:after{content:'';position:absolute;top:100%;right:30px;border:13px solid transparent;border-top-color:rgba(0,0,0,0.04);}.learn-bar>.learn{position:absolute;width:272px;top:8px;left:-300px;padding:10px;border-radius:5px;background-color:rgba(255,255,255,0.6);-webkit-transition-property:left;transition-property:left;-webkit-transition-duration:500ms;transition-duration:500ms;}@media (min-width:899px){.learn-bar{width:auto;margin:0 0 0 300px;}.learn-bar>.learn{left:8px;}.learn-bar #todoapp{width:550px;margin:130px auto 40px auto;}}
\ No newline at end of file
(function(){function i(){var a=location.href.indexOf("examples/");return location.href.substr(0,a)}function f(a,b){if(!(this instanceof f))return new f(a,b);var c,d;if("object"!==typeof a)try{a=JSON.parse(a)}catch(k){return}if(b)c=b.template,d=b.framework;if(!c&&a.templates)c=a.templates.todomvc;if(!d&&document.querySelector("[data-framework]"))d=document.querySelector("[data-framework]").dataset.framework;this.template=c;if(a.backend)this.frameworkJSON=a.backend,this.append({backend:!0});else if(a[d])this.frameworkJSON=
a[d],this.append()}var m=function(a){a.defaults=function(a){if(!a)return a;for(var b=1,c=arguments.length;b<c;b++){var d=arguments[b];if(d)for(var j in d)null==a[j]&&(a[j]=d[j])}return a};a.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var b=/(.)^/,c={"'":"'","\\":"\\","\r":"r","\n":"n","\t":"t","\u2028":"u2028","\u2029":"u2029"},d=/\\|'|\r|\n|\t|\u2028|\u2029/g;a.template=function(k,h,g){var f,g=a.defaults({},g,a.templateSettings),j=RegExp([(g.escape||
b).source,(g.interpolate||b).source,(g.evaluate||b).source].join("|")+"|$","g"),i=0,e="__p+='";k.replace(j,function(a,b,h,f,g){e+=k.slice(i,g).replace(d,function(a){return"\\"+c[a]});b&&(e+="'+\n((__t=("+b+"))==null?'':_.escape(__t))+\n'");h&&(e+="'+\n((__t=("+h+"))==null?'':__t)+\n'");f&&(e+="';\n"+f+"\n__p+='");i=g+a.length;return a});e+="';\n";g.variable||(e="with(obj||{}){\n"+e+"}\n");e="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+e+"return __p;\n";
try{f=new Function(g.variable||"obj","_",e)}catch(l){throw l.source=e,l;}if(h)return f(h,a);h=function(b){return f.call(this,b,a)};h.source="function("+(g.variable||"obj")+"){\n"+e+"}";return h};return a}({});if("todomvc.com"===location.hostname)window._gaq=[["_setAccount","UA-31081062-1"],["_trackPageview"]],function(a,b){var c=a.createElement(b),d=a.getElementsByTagName(b)[0];c.src="//www.google-analytics.com/ga.js";d.parentNode.insertBefore(c,d)}(document,"script");f.prototype.append=function(a){var b=
document.createElement("aside");b.innerHTML=m.template(this.template,this.frameworkJSON);b.className="learn";if(a&&a.backend){var a=b.querySelector(".source-links"),c=a.firstElementChild,d=a.lastElementChild,f=d.getAttribute("href");d.setAttribute("href",f.substr(f.lastIndexOf("http")));a.innerHTML=c.outerHTML+d.outerHTML}else a=b.querySelectorAll(".demo-link"),Array.prototype.forEach.call(a,function(a){"http"!==a.getAttribute("href").substr(0,4)&&a.setAttribute("href",i()+a.getAttribute("href"))});
document.body.className=(document.body.className+" learn-bar").trim();document.body.insertAdjacentHTML("afterBegin",b.outerHTML)};if("tastejs.github.io"===location.hostname)location.href=location.href.replace("tastejs.github.io/todomvc","todomvc.com");(function(a,b){if(!location.host)return console.info("Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.");var c=new XMLHttpRequest;c.open("GET",i()+a,!0);c.send();c.onload=function(){200===c.status&&b&&b(c.responseText)}})("learn.json",
f)})();/*
DUEL v0.8.2 http://duelengine.org
Copyright (c)2006-2012 Stephen M. McKamey.
Licensed under The MIT License.
*/
var duel=function(m,u,w){function x(a){this.value=a}function l(a){switch(typeof a){case "object":return!a?0:y(a)?2:a instanceof x?5:a instanceof Date?4:3;case "function":return 1;case "undefined":return 0;default:return 4}}function n(a){return"function"===typeof a}function o(){this.value=o.FAST?"":[]}function q(a){y(a)||(a=["",a]);this.value=a}function r(a,b){switch(l(b)){case 2:if(""===b[0])for(var c=1,f=b.length;c<f;c++)r(a,b[c]);else a.push(b);break;case 3:if(1===a.length)a.push(b);else if(c=a[1],
3===l(c))for(f in b)b.hasOwnProperty(f)&&(c[f]=b[f]);else a.splice(1,0,b);break;case 4:""!==b&&(b=""+b,c=a.length-1,0<c&&4===l(a[c])?a[c]+=b:a.push(b));break;case 0:break;default:a.push(b)}}function s(a,b,c,f,d,e){var g=3===l(a[1]);if(a.length===(g?3:2))return k(a[a.length-1],b,c,f,d,e);for(var h=[""],g=g?2:1,j=a.length;g<j;g++)r(h,k(a[g],b,c,f,d,e));return h}function A(a,b,c,f,d,e){for(var g=1,h=a.length;g<h;g++){var j=a[g],i=j[1].test;if(3===l(j[1])&&i&&(n(i)&&(i=i(b,c,f,d)),!i))continue;return s(j,
b,c,f,d,e)}return null}function J(a){2!==l(a)&&(a=["",a]);var b=function(b,f,d,e){try{var g=k(a,b,isFinite(f)?f:0,isFinite(d)?d:1,"string"===typeof e?e:null);return new q(g)}catch(h){return new q("["+h+"]")}};b.getView=function(){return a};return b}function K(a){return"string"!==typeof a?a:a.replace(/[&<>]/g,function(a){switch(a){case "&":return"&amp;";case "<":return"&lt;";case ">":return"&gt;";default:return a}})}function L(a){return"string"!==typeof a?a:a.replace(/[&<>"]/g,function(a){switch(a){case "&":return"&amp;";
case "<":return"&lt;";case ">":return"&gt;";case '"':return"&quot;";default:return a}})}function B(a,b){var c=b[0]||"",f=b.length,d=1,e,g=M[c];if("!"===c.charAt(0))"!DOCTYPE"===b[0]?a.append("<!DOCTYPE ",b[1],">"):a.append("<\!--",b[1],"--\>");else{if(c){a.append("<",c);e=b[d];if(3===l(e)){for(var h in e)if(e.hasOwnProperty(h)){var j=e[h];if(C[h])if(j)j=h;else continue;a.append(" ",h);0!==l(j)&&a.append('="',L(j),'"')}d++}g&&a.append(" /");a.append(">")}for(;d<f;d++)e=b[d],y(e)?B(a,e):a.append(K(e));
c&&!g&&a.append("</",c,">")}}function t(a){if(a){if("!"===a.charAt(0))return m.createComment("!"===a?"":a.substr(1)+" ")}else{if(m.createDocumentFragment)return m.createDocumentFragment();a=""}return"style"===a.toLowerCase()&&m.createStyleSheet?m.createStyleSheet():m.createElement(a)}function v(a,b){if(b){var c=(a.tagName||"").toLowerCase();if(8===a.nodeType)3===b.nodeType&&(a.nodeValue+=b.nodeValue);else if("table"===c&&a.tBodies)if(b.tagName)if((c=b.tagName.toLowerCase())&&"tbody"!==c&&"thead"!==
c){var f=0<a.tBodies.length?a.tBodies[a.tBodies.length-1]:null;f||(f=t("th"===c?"thead":"tbody"),a.appendChild(f));f.appendChild(b)}else!1!==a.canHaveChildren&&a.appendChild(b);else{if(11===b.nodeType)for(;b.firstChild;)v(a,b.removeChild(b.firstChild))}else if("style"===c&&m.createStyleSheet)a.cssText=b;else if(!1!==a.canHaveChildren)a.appendChild(b);else if("object"===c&&b.tagName&&"param"===b.tagName.toLowerCase()){try{a.appendChild(b)}catch(d){}try{if(a.object)a.object[b.name]=b.value}catch(e){}}}}
function D(a,b,c){switch(typeof c){case "function":a.addEventListener?a.addEventListener("on"===b.substr(0,2)?b.substr(2):b,c,!1):a[b]=c;break;case "string":a[b]=new Function("event",c)}}function E(a){return!!a&&3===a.nodeType&&(!a.nodeValue||!/\S/.exec(a.nodeValue))}function F(a,b){if(a&&3===a.nodeType&&b.exec(a.nodeValue))a.nodeValue=a.nodeValue.replace(b,"")}function z(a){if(a){for(;E(a.firstChild);)a.removeChild(a.firstChild);for(F(a.firstChild,N);E(a.lastChild);)a.removeChild(a.lastChild);F(a.lastChild,
O)}}function G(a,b){var c=a[b];if(c){try{delete a[b]}catch(f){a[b]=w}if(!n(c))try{c=new Function(""+c)}catch(d){c=null}}return c}function H(a){if(a){var b=G(a,"$init");b&&b.call(a);(b=G(a,"$load"))?setTimeout(function(){b.call(a);b=a=null},0):b=a=null}}function I(a,b){for(var c=1,f=b.length;c<f;c++){var d=b[c];switch(l(d)){case 2:var e=d[0],d=I(t(e),d);if("html"===e)return z(d),H(d),d;v(a,d);break;case 4:""!==d&&v(a,m.createTextNode(""+d));break;case 3:if(1===a.nodeType){var e=a,g=d;if(g.name&&m.attachEvent&&
!e.parentNode)try{var h=t("<"+e.tagName+' name="'+g.name+'">');e.tagName===h.tagName&&(e=h)}catch(j){}d=void 0;for(d in g)if(g.hasOwnProperty(d)){var i=g[d],k=l(i);if(d)0===k&&(i="",k=4),d=P[d.toLowerCase()]||d,C[d]?(e[d]=!!i,p[d]&&(e[p[d]]=!!i)):"style"===d?"undefined"!==typeof e.style.cssText?e.style.cssText=i:e.style=i:"class"===d?e.className=i:"on"===d.substr(0,2)?(D(e,d,i),p[d]&&D(e,p[d],i)):4===k&&"$"!==d.charAt(0)?(e.setAttribute(d,i),p[d]&&e.setAttribute(p[d],i)):(e[d]=i,p[d]&&(e[p[d]]=i))}a=
e}break;case 5:e=v;g=a;i=d;d=t("div");d.innerHTML=""+i;z(d);if(1===d.childNodes.length)d=d.firstChild;else{for(i=t("");d.firstChild;)i.appendChild(d.firstChild);d=i}e(g,d)}}z(a);H(a);if(11===a.nodeType&&1===a.childNodes.length)a=a.firstChild;return a}x.prototype.toString=function(){return this.value};var y=Array.isArray||function(a){return a instanceof Array};o.FAST=!(u&&9>u());o.prototype.append=function(a,b,c){o.FAST?null!==a&&(this.value+=a,null!==b&&b!==w&&(this.value+=b,null!==c&&c!==w&&(this.value+=
c))):this.value.push.apply(this.value,arguments)};o.prototype.clear=function(){this.value=o.FAST?"":[]};o.prototype.toString=function(){return o.FAST?this.value:this.value.join("")};var k;k=function(a,b,c,f,d,e){switch(l(a)){case 1:return a(b,c,f,d);case 2:var g=a[0]||"";switch(g){case "$for":a:{var h=a[1]||{},g=[""],j;if(h.hasOwnProperty("count")){j=h.count;n(j)&&(j=j(b,c,f,d));h.hasOwnProperty("data")?(h=h.data,n(h)&&(h=h(b,c,f,d))):h=b;for(b=0;b<j;b++)r(g,s(a,h,b,j,null,e))}else{if(h.hasOwnProperty("in")){var i=
h["in"];n(i)&&(i=i(b,c,f,d));if(3===l(i)){h=[];for(j in i)i.hasOwnProperty(j)&&h.push(j);for(b=0,j=h.length;b<j;b++)r(g,s(a,i[h[b]],b,j,h[b],e));a=g;break a}h=i}else h=h.each,n(h)&&(h=h(b,c,f,d));b=l(h);if(2===b)for(b=0,j=h.length;b<j;b++)r(g,s(a,h[b],b,j,null,e));else 0!==b&&(g=s(a,h,0,1,null,e))}a=g}return a;case "$xor":return A(a,b,c,f,d,e);case "$if":return A(["$xor",a],b,c,f,d,e);case "$call":e=a[1]||{};if(e.view){g=k(e.view,b,c,f,d);h=e.hasOwnProperty("data")?k(e.data,b,c,f,d):b;j=e.hasOwnProperty("index")?
k(e.index,b,c,f,d):c;i=e.hasOwnProperty("count")?k(e.count,b,c,f,d):f;b=e.hasOwnProperty("key")?k(e.key,b,c,f,d):d;c={};for(f=a.length-1;2<=f;f--)d=a[f],e=d[1]||{},e.hasOwnProperty("name")&&(c[e.name]=d);a=g&&n(g.getView)?k(g.getView(),h,j,i,b,c):null}else a=null;return a;case "$part":return g=(a[1]||{}).name||"",g=e&&e.hasOwnProperty(g)?e[g]:a,s(g,b,c,f,d)}g=[g];h=1;for(j=a.length;h<j;h++)r(g,k(a[h],b,c,f,d,e));return g;case 3:e={};for(g in a)a.hasOwnProperty(g)&&(e[g]=k(a[g],b,c,f,d));return e}return a};
u=function(a){return n(a)&&n(a.getView)?a:J(a)};u.raw=function(a){return new x(a)};var M={area:!0,base:!0,basefont:!0,br:!0,col:!0,frame:!0,hr:!0,img:!0,input:!0,isindex:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,wbr:!0},C={async:1,checked:1,defer:1,disabled:1,hidden:1,novalidate:1,formnovalidate:1};q.prototype.toString=function(){var a;var b=this.value;try{var c=new o;B(c,b);a=c.toString()}catch(f){a="["+f+"]"}return a};q.prototype.write=function(a){(a||m).write(""+this)};var P={rowspan:"rowSpan",
colspan:"colSpan",cellpadding:"cellPadding",cellspacing:"cellSpacing",tabindex:"tabIndex",accesskey:"accessKey",hidefocus:"hideFocus",usemap:"useMap",maxlength:"maxLength",readonly:"readOnly",contenteditable:"contentEditable"},p={enctype:"encoding",onscroll:"DOMMouseScroll",checked:"defaultChecked"},N=/^[\r\n]+/,O=/[\r\n]+$/;q.prototype.toDOM=function(a,b){4===l(a)&&(a=m.getElementById(a));var c;try{b&&(c=a,a=null),c=I(c||t(this.value[0]),this.value)}catch(f){c=m.createTextNode("["+f+"]")}a&&a.parentNode&&
a.parentNode.replaceChild(c,a);return c};q.prototype.reload=function(){var a=m;try{var b=this.toDOM();a.replaceChild(b,a.documentElement);if(a.createStyleSheet){for(var c=b.firstChild;c&&"HEAD"!==(c.tagName||"");)c=c.nextSibling;for(var f=c&&c.firstChild;f;){if("LINK"===(f.tagName||""))f.href=f.href;f=f.nextSibling}}}catch(d){a=a.open("text/html"),a.write(this.toString()),a.close()}};return u}(document,window.ScriptEngineMajorVersion);var todos=todos||{};
(function(h,e,g){function d(){var a="undefined"!==typeof JSON?JSON.stringify(b):b;e.setItem(g,a)}var b,e=e||function(){var a={};return{getItem:function(c){return a[c]},setItem:function(c,b){a[c]=b}}}(),f=e.getItem(g);b=f?"undefined"!==typeof JSON?JSON.parse(f):f:[];h.model={tasks:function(){return b},stats:function(){var a={};a.total=b.length;a.completed=b.filter(function(a){return a.completed}).length;a.active=a.total-a.completed;return a},add:function(a){a={id:((new Date).getTime()+Math.random()).toString(36),
title:a,completed:!1};b.push(a);d();return a},edit:function(a,c){b.filter(function(b){return b.id===a})[0].title=c;d()},toggle:function(a,c){b.filter(function(b){return b.id===a})[0].completed=c;d()},toggleAll:function(a){b.forEach(function(b){b.completed=a});d()},remove:function(a){b.forEach(function(c,d){c.id===a&&b.splice(d,1)});d()},expunge:function(){b.forEach(function(a,c){a.completed&&b.splice(c,1)});d()}}})(todos,window.localStorage,"todos-duel");var todos=todos||{};todos.views=todos.views||{};todos.views.Stats=duel([""," ",["$if",{test:function(a){return a.total}},["footer",{id:"footer"}," ",["span",{id:"todo-count"},["strong",function(a){return a.active}]," ",function(a){return 1===a.active?"item":"items"}," left"]," "," ",["$if",{test:function(a){return a.completed}},["button",{id:"clear-completed",onclick:function(){return todos.actions.clear_click}},"Clear completed"]]]]]);var todos=todos||{};todos.views=todos.views||{};
todos.views.Task=duel([""," ",["li",{"class":function(a){return a.completed?"completed":""}}," ",["div",{"class":"view"}," ",["input",{"class":"toggle",type:"checkbox",checked:function(a){return a.completed},onchange:function(a){return todos.actions.completed_change(a.id)}}]," ",["label",{ondblclick:function(a){return todos.actions.content_dblclick(a.id)}},function(a){return a.title}]," ",["button",{"class":"destroy",onclick:function(a){return todos.actions.remove_click(a.id)}}]," "]," ",["input",
{"class":"edit",type:"text",value:function(a){return a.title},onblur:function(a){return todos.actions.edit_blur(a.id)},onkeypress:function(a){return todos.actions.edit_keypress(a.id)}}]]]);var todos=todos||{};todos.views=todos.views||{};todos.views.Tasks=duel([""," ",["$if",{test:function(a){return a.tasks.length}},["section",{id:"main"}," ",["input",{id:"toggle-all",type:"checkbox",checked:function(a){return!a.stats.active},onchange:function(){return todos.actions.toggle_change}}]," ",["label",{"for":"toggle-all"},"Mark all as complete"]," ",["ul",{id:"todo-list"}," ",["$for",{each:function(a){return a.tasks}}," ",["$call",{view:function(){return todos.views.Task}}]]," "]]]]);var todos=todos||{};todos.views=todos.views||{};todos.views.TodoApp=duel(["section",{id:"todoapp"}," ",["header",{id:"header"}," ",["h1","todos"]," ",["input",{id:"new-todo",placeholder:"What needs to be done?",autofocus:null,onblur:function(){return todos.actions.add_blur},onkeypress:function(){return todos.actions.add_keypress}}]," "]," ",["$call",{view:function(){return todos.views.Tasks},data:function(a){return a}}]," ",["$call",{view:function(){return todos.views.Stats},data:function(a){return a.stats}}]]);var todos=todos||{};
(function(c,e){function f(a){var a=a||c.model.stats(),a=c.views.Stats(a).toDOM(),b=e.getElementById(i);b?b.parentNode.replaceChild(a,b):e.getElementById(g).appendChild(a)}function d(){var a={tasks:c.model.tasks(),stats:c.model.stats()},b=c.views.Tasks(a).toDOM(),d=e.getElementById(j);d?d.parentNode.replaceChild(b,d):e.getElementById(g).appendChild(b);f(a.stats)}function h(a){var b=(a.value||"").trim();a.value="";b&&(a=c.model.add(b),(b=e.getElementById(k))?(b.appendChild(c.views.Task(a).toDOM()),f()):
d())}var i="footer",g="todoapp",j="main",k="todo-list";c.actions={addBlur:function(){h(this)},add_keypress:function(a){13===a.keyCode&&h(this)},edit_blur:function(a){return function(){var b=(this.value||"").trim();(this.value=b)?c.model.edit(a,b):c.model.remove(a);d()}},edit_keypress:function(){return function(a){13===a.keyCode&&this.blur()}},remove_click:function(a){return function(){c.model.remove(a);d()}},clear_click:function(){c.model.expunge();d()},content_dblclick:function(){var a=function(b){if("LI"!==
b.tagName)return a(b.parentNode);b.className="editing";b=b.getElementsByTagName("input")[1];b.focus();b.value=b.value};return function(){a(this)}},completed_change:function(a){return function(){c.model.toggle(a,this.checked);d()}},toggle_change:function(){c.model.toggleAll(this.checked);d()}};(function(a){var b=c.views.TodoApp({tasks:c.model.tasks(),stats:c.model.stats()}).toDOM();a.insertBefore(b,a.firstChild)})(e.body)})(todos,window.document);
\ No newline at end of file
<!DOCTYPE html> <!doctype html>
<html lang="en" data-framework="duel"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8">
<title>DUEL &#x2022; TodoMVC</title> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./cdn/bada2673af7edc043a573c3e20ac2d8ce9d10037.css" /> <title>DUEL &bull; TodoMVC</title>
<link rel="stylesheet" href="./cdn/b12c1274056c76efb21a375280fdd622eb22b845.css">
</head> </head>
<body> <body>
<footer id="info"> <footer class="info">
<p>Double-click to edit a todo</p> <p>Double-click to edit a todo</p>
<p>Ported to <a href="http://duelengine.org">DUEL</a> by <a href="http://mck.me">Stephen McKamey</a></p> <p>Ported to <a href="http://duelengine.org">DUEL</a> by <a href="http://mck.me">Stephen McKamey</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer> </footer>
<script src="./cdn/d10574a720ccdf2d2c810f0c45afb5f159264a1f.js"></script> <script src="./cdn/3aba0c24fc2dfb2ef530691bf611672891b75c6d.js"></script>
</body> </body>
</html> </html>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment