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;
}
}
# todomvc base file # todomvc base files
/bower_components/todomvc-common/base.css /css/base.css
/css/index.css
/* 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": "./",
......
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
<!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