Compare commits
2 commits
master
...
benchmarks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d571dd2e56 | ||
|
|
02e7b40c07 |
242 changed files with 71311 additions and 0 deletions
2
bench/.gitignore
vendored
Normal file
2
bench/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
elm-stuff
|
||||||
|
node_modules
|
||||||
27
bench/LICENCE
Normal file
27
bench/LICENCE
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2016, Evan Czaplicki
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the {organization} nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
15
bench/elm-package.json
Normal file
15
bench/elm-package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"summary": "helpful summary of your project, less than 80 characters",
|
||||||
|
"repository": "https://github.com/user/project.git",
|
||||||
|
"license": "BSD3",
|
||||||
|
"source-directories": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"exposed-modules": [],
|
||||||
|
"dependencies": {
|
||||||
|
"elm-lang/core": "4.0.5 <= v < 5.0.0",
|
||||||
|
"elm-lang/html": "1.1.0 <= v < 2.0.0"
|
||||||
|
},
|
||||||
|
"elm-version": "0.17.1 <= v < 0.18.0"
|
||||||
|
}
|
||||||
1
bench/implementations/angular-1.5.8-optimized/build.min.js
vendored
Executable file
1
bench/implementations/angular-1.5.8-optimized/build.min.js
vendored
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
(function(){"use strict";angular.module("todoCtrl",[]).controller("TodoCtrl",function TodoCtrl($scope,$location){var TC=this;var todos=TC.todos=[];TC.ESCAPE_KEY=27;TC.editedTodo={};TC.updateRemainingCount=function(){TC.remainingCount=todos.filter(function(todo){return!todo.completed}).length;TC.allChecked=TC.remainingCount===0};function resetTodo(){TC.newTodo={title:"",completed:false}}resetTodo();if($location.path()===""){$location.path("/")}TC.location=$location;$scope.$watch("TC.location.path()",function(path){TC.statusFilter={"/active":{completed:false},"/completed":{completed:true}}[path]});TC.addTodo=function(){var newTitle=TC.newTodo.title=TC.newTodo.title.trim();if(newTitle.length===0){return}todos.push(TC.newTodo);resetTodo();TC.updateRemainingCount()};TC.editTodo=function(todo){TC.editedTodo=todo;TC.originalTodo=angular.copy(todo)};TC.doneEditing=function(todo,index){TC.editedTodo={};todo.title=todo.title.trim();if(!todo.title){TC.removeTodo(index)}TC.updateRemainingCount()};TC.revertEditing=function(index){TC.editedTodo={};todos[index]=TC.originalTodo;TC.updateRemainingCount()};TC.removeTodo=function(index){todos.splice(index,1);TC.updateRemainingCount()};TC.clearCompletedTodos=function(){TC.todos=todos=todos.filter(function(val){return!val.completed});TC.updateRemainingCount()};TC.markAll=function(completed){todos.forEach(function(todo){todo.completed=completed});TC.updateRemainingCount()}});angular.module("todoFocus",[]).directive("todoFocus",function($timeout){return function(scope,elem,attrs){scope.$watch(attrs.todoFocus,function(newVal){if(newVal){$timeout(function(){elem[0].focus()},0,false)}})}});angular.module("todomvc",["todoCtrl","todoFocus"])})();
|
||||||
77
bench/implementations/angular-1.5.8-optimized/index.html
Executable file
77
bench/implementations/angular-1.5.8-optimized/index.html
Executable file
|
|
@ -0,0 +1,77 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" ng-app="todomvc" data-framework="angularjs">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>AngularJS • TodoMVC</title>
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
|
||||||
|
<style>
|
||||||
|
[ng-cloak] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section class="todoapp" ng-controller="TodoCtrl as TC">
|
||||||
|
<header class="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<form id="todo-form">
|
||||||
|
<input class="new-todo" placeholder="What needs to be done?" ng-model="TC.newTodo.title" autofocus ng-keydown="($event.keyCode === 13) && TC.addTodo()">
|
||||||
|
</form>
|
||||||
|
</header>
|
||||||
|
<section class="main" ng-show="TC.todos.length" ng-cloak>
|
||||||
|
<input class="toggle-all" type="checkbox" ng-model="TC.allChecked" ng-click="TC.markAll(TC.allChecked)">
|
||||||
|
<label for="toggle-all">Mark all as complete</label>
|
||||||
|
<ul class="todo-list">
|
||||||
|
<li ng-repeat="todo in TC.todos | filter:TC.statusFilter" ng-class="{completed: todo.completed, editing: todo === TC.editedTodo}">
|
||||||
|
<div class="view">
|
||||||
|
<input class="toggle" type="checkbox" ng-model="todo.completed" ng-change="TC.updateAndStore()">
|
||||||
|
<label ng-dblclick="TC.editTodo(todo)">{{todo.title}}</label>
|
||||||
|
<button class="destroy" ng-click="TC.removeTodo($index)"></button>
|
||||||
|
</div>
|
||||||
|
<form ng-submit="TC.doneEditing(todo, $index)">
|
||||||
|
<input class="edit" ng-trim="false" ng-model="todo.title" ng-blur="TC.doneEditing(todo, $index)" ng-keydown="($event.keyCode === TC.ESCAPE_KEY) && TC.revertEditing($index)" todo-focus="todo === TC.editedTodo">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<footer class="footer" ng-show="TC.todos.length" ng-cloak>
|
||||||
|
<span class="todo-count"><strong>{{TC.remainingCount}}</strong>
|
||||||
|
<ng-pluralize count="TC.remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
|
||||||
|
</span>
|
||||||
|
<ul class="filters">
|
||||||
|
<li>
|
||||||
|
<a ng-class="{selected: TC.location.path() == '/'} " href="#/">All</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a ng-class="{selected: TC.location.path() == '/active'}" href="#/active">Active</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a ng-class="{selected: TC.location.path() == '/completed'}" href="#/completed">Completed</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button class="clear-completed" ng-click="TC.clearCompletedTodos()" ng-show="TC.remainingCount < TC.todos.length">Clear completed</button>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
<footer class="info">
|
||||||
|
<p>Double-click to edit a todo</p>
|
||||||
|
<p>Credits:
|
||||||
|
<a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>,
|
||||||
|
<a href="http://ericbidelman.com">Eric Bidelman</a>,
|
||||||
|
<a href="http://jacobmumm.com">Jacob Mumm</a> and
|
||||||
|
<a href="http://blog.igorminar.com">Igor Minar</a>
|
||||||
|
</p>
|
||||||
|
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
window.requestAnimationFrame = window.webkitRequestAnimationFrame = function(callback) {
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="node_modules/angular/angular.js"></script>
|
||||||
|
<script src="build.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
118
bench/implementations/angular-1.5.8-optimized/js/app.js
Executable file
118
bench/implementations/angular-1.5.8-optimized/js/app.js
Executable file
|
|
@ -0,0 +1,118 @@
|
||||||
|
/* jshint undef: true, unused: true */
|
||||||
|
/*global angular */
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('todoCtrl', [])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main controller for the app. The controller:
|
||||||
|
* - retrieves and persists the model via the todoStorage service
|
||||||
|
* - exposes the model to the template and provides event handlers
|
||||||
|
*/
|
||||||
|
.controller('TodoCtrl', function TodoCtrl($scope, $location) {
|
||||||
|
var TC = this;
|
||||||
|
var todos = TC.todos = [];
|
||||||
|
|
||||||
|
TC.ESCAPE_KEY = 27;
|
||||||
|
TC.editedTodo = {};
|
||||||
|
|
||||||
|
TC.updateRemainingCount = function () {
|
||||||
|
TC.remainingCount = todos.filter(function (todo) { return !todo.completed; }).length;
|
||||||
|
TC.allChecked = (TC.remainingCount === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetTodo() {
|
||||||
|
TC.newTodo = {title: '', completed: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTodo();
|
||||||
|
|
||||||
|
if ($location.path() === '') {
|
||||||
|
$location.path('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
TC.location = $location;
|
||||||
|
|
||||||
|
$scope.$watch('TC.location.path()', function (path) {
|
||||||
|
TC.statusFilter = { '/active': {completed: false}, '/completed': {completed: true} }[path];
|
||||||
|
});
|
||||||
|
|
||||||
|
TC.addTodo = function () {
|
||||||
|
var newTitle = TC.newTodo.title = TC.newTodo.title.trim();
|
||||||
|
if (newTitle.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
todos.push(TC.newTodo);
|
||||||
|
resetTodo();
|
||||||
|
TC.updateRemainingCount();
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.editTodo = function (todo) {
|
||||||
|
TC.editedTodo = todo;
|
||||||
|
|
||||||
|
// Clone the original todo to restore it on demand.
|
||||||
|
TC.originalTodo = angular.copy(todo);
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.doneEditing = function (todo, index) {
|
||||||
|
TC.editedTodo = {};
|
||||||
|
todo.title = todo.title.trim();
|
||||||
|
|
||||||
|
if (!todo.title) {
|
||||||
|
TC.removeTodo(index);
|
||||||
|
}
|
||||||
|
TC.updateRemainingCount();
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.revertEditing = function (index) {
|
||||||
|
TC.editedTodo = {};
|
||||||
|
todos[index] = TC.originalTodo;
|
||||||
|
TC.updateRemainingCount();
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.removeTodo = function (index) {
|
||||||
|
todos.splice(index, 1);
|
||||||
|
TC.updateRemainingCount();
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.clearCompletedTodos = function () {
|
||||||
|
TC.todos = todos = todos.filter(function (val) {
|
||||||
|
return !val.completed;
|
||||||
|
});
|
||||||
|
TC.updateRemainingCount();
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.markAll = function (completed) {
|
||||||
|
todos.forEach(function (todo) {
|
||||||
|
todo.completed = completed;
|
||||||
|
});
|
||||||
|
TC.updateRemainingCount();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
angular.module('todoFocus', [])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directive that places focus on the element it is applied to when the expression it binds to evaluates to true
|
||||||
|
*/
|
||||||
|
.directive('todoFocus', function ($timeout) {
|
||||||
|
return function (scope, elem, attrs) {
|
||||||
|
scope.$watch(attrs.todoFocus, function (newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
$timeout(function () {
|
||||||
|
elem[0].focus();
|
||||||
|
}, 0, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* The main TodoMVC app module that pulls all dependency modules declared in same named files
|
||||||
|
*
|
||||||
|
* @type {angular.Module}
|
||||||
|
*/
|
||||||
|
angular.module('todomvc', ['todoCtrl', 'todoFocus']);
|
||||||
|
})();
|
||||||
14
bench/implementations/angular-1.5.8-optimized/package.json
Normal file
14
bench/implementations/angular-1.5.8-optimized/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"make": "uglifyjs js/app.js -o build.min.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"angular": "^1.5.8",
|
||||||
|
"todomvc-app-css": "^2.0.6",
|
||||||
|
"todomvc-common": "^1.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"uglify-js": "^2.6.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
bench/implementations/angular-1.5.8-optimized/readme.md
Executable file
25
bench/implementations/angular-1.5.8-optimized/readme.md
Executable file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Angular TodoMVC for Benchmarking
|
||||||
|
|
||||||
|
This is an update of the
|
||||||
|
_[Angular TodoMVC example](https://github.com/tastejs/todomvc/tree/gh-pages/examples/angularjs-perf)_
|
||||||
|
with the following changes:
|
||||||
|
|
||||||
|
* Upgrade to Angular 1.5.7
|
||||||
|
* Update todomvc-app-css to 2.0.6 to match other examples in this benchmark
|
||||||
|
* Remove localStorage functionality - not relevant to measuring render performance
|
||||||
|
* Concat JS into one file
|
||||||
|
* Minify with uglify
|
||||||
|
|
||||||
|
and additional performance optimizations:
|
||||||
|
|
||||||
|
#### Removed the deep $watch in favor of direct function calls
|
||||||
|
The original example uses $scope.$watch with object equality enabled to track changes to the array of todos and perform actions when changes are detected. This is not a good practice as it causes a deep array comparison to happen every digest cycle, which can significantly degrade performance when working with large collections. Instead we can perform the required actions directly whenever todos are modified, since we control all points of possible modification.
|
||||||
|
|
||||||
|
#### Removed "track by $index" from the ng-repeat directive
|
||||||
|
The original example uses the ng-repeat directive with a "track by $index" clause to display the list of todos. _[Refer to section "Tracking and Duplicates" here for information on using "track by".](https://code.angularjs.org/1.5.7/docs/api/ng/directive/ngRepeat)_ In this particular case it is not necessary to use it, as long as care is taken not to persist the "$$hashKey" property added by Angular to local storage, which would cause duplicates to appear when the collection is loaded and new items are added, triggering an exception. The version without the "track by" clause could reasonably be written by someone implementing this from scratch and demonstrates better performance.
|
||||||
|
|
||||||
|
# Building
|
||||||
|
|
||||||
|
1. `npm install`
|
||||||
|
2. `npm run make`
|
||||||
|
3. Open a local server (e.g. with `npm install -g http-server`) and open index.html
|
||||||
1
bench/implementations/angular-1.5.8/build.min.js
vendored
Normal file
1
bench/implementations/angular-1.5.8/build.min.js
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
(function(){"use strict";angular.module("todoCtrl",[]).controller("TodoCtrl",function TodoCtrl($scope,$location){var TC=this;var todos=TC.todos=[];TC.ESCAPE_KEY=27;TC.editedTodo={};function resetTodo(){TC.newTodo={title:"",completed:false}}resetTodo();if($location.path()===""){$location.path("/")}TC.location=$location;$scope.$watch("TC.location.path()",function(path){TC.statusFilter={"/active":{completed:false},"/completed":{completed:true}}[path]});$scope.$watch("TC.todos",function(){TC.remainingCount=todos.filter(function(todo){return!todo.completed}).length;TC.allChecked=TC.remainingCount===0},true);TC.addTodo=function(){var newTitle=TC.newTodo.title=TC.newTodo.title.trim();if(newTitle.length===0){return}todos.push(TC.newTodo);resetTodo()};TC.editTodo=function(todo){TC.editedTodo=todo;TC.originalTodo=angular.copy(todo)};TC.doneEditing=function(todo,index){TC.editedTodo={};todo.title=todo.title.trim();if(!todo.title){TC.removeTodo(index)}};TC.revertEditing=function(index){TC.editedTodo={};todos[index]=TC.originalTodo};TC.removeTodo=function(index){todos.splice(index,1)};TC.clearCompletedTodos=function(){TC.todos=todos=todos.filter(function(val){return!val.completed})};TC.markAll=function(completed){todos.forEach(function(todo){todo.completed=completed})}});angular.module("todoFocus",[]).directive("todoFocus",function($timeout){return function(scope,elem,attrs){scope.$watch(attrs.todoFocus,function(newVal){if(newVal){$timeout(function(){elem[0].focus()},0,false)}})}});angular.module("todomvc",["todoCtrl","todoFocus"])})();
|
||||||
77
bench/implementations/angular-1.5.8/index.html
Normal file
77
bench/implementations/angular-1.5.8/index.html
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" ng-app="todomvc" data-framework="angularjs">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>AngularJS • TodoMVC</title>
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
|
||||||
|
<style>
|
||||||
|
[ng-cloak] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section class="todoapp" ng-controller="TodoCtrl as TC">
|
||||||
|
<header class="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<form id="todo-form">
|
||||||
|
<input class="new-todo" placeholder="What needs to be done?" ng-model="TC.newTodo.title" autofocus ng-keydown="($event.keyCode === 13) && TC.addTodo()">
|
||||||
|
</form>
|
||||||
|
</header>
|
||||||
|
<section class="main" ng-show="TC.todos.length" ng-cloak>
|
||||||
|
<input class="toggle-all" type="checkbox" ng-model="TC.allChecked" ng-click="TC.markAll(TC.allChecked)">
|
||||||
|
<label for="toggle-all">Mark all as complete</label>
|
||||||
|
<ul class="todo-list">
|
||||||
|
<li ng-repeat="todo in TC.todos | filter:TC.statusFilter track by $index" ng-class="{completed: todo.completed, editing: todo === TC.editedTodo}">
|
||||||
|
<div class="view">
|
||||||
|
<input class="toggle" type="checkbox" ng-model="todo.completed">
|
||||||
|
<label ng-dblclick="TC.editTodo(todo)">{{todo.title}}</label>
|
||||||
|
<button class="destroy" ng-click="TC.removeTodo($index)"></button>
|
||||||
|
</div>
|
||||||
|
<form ng-submit="TC.doneEditing(todo, $index)">
|
||||||
|
<input class="edit" ng-trim="false" ng-model="todo.title" ng-blur="TC.doneEditing(todo, $index)" ng-keydown="($event.keyCode === TC.ESCAPE_KEY) && TC.revertEditing($index)" todo-focus="todo === TC.editedTodo">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<footer class="footer" ng-show="TC.todos.length" ng-cloak>
|
||||||
|
<span class="todo-count"><strong>{{TC.remainingCount}}</strong>
|
||||||
|
<ng-pluralize count="TC.remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
|
||||||
|
</span>
|
||||||
|
<ul class="filters">
|
||||||
|
<li>
|
||||||
|
<a ng-class="{selected: TC.location.path() == '/'} " href="#/">All</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a ng-class="{selected: TC.location.path() == '/active'}" href="#/active">Active</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a ng-class="{selected: TC.location.path() == '/completed'}" href="#/completed">Completed</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button class="clear-completed" ng-click="TC.clearCompletedTodos()" ng-show="TC.remainingCount < TC.todos.length">Clear completed</button>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
<footer class="info">
|
||||||
|
<p>Double-click to edit a todo</p>
|
||||||
|
<p>Credits:
|
||||||
|
<a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>,
|
||||||
|
<a href="http://ericbidelman.com">Eric Bidelman</a>,
|
||||||
|
<a href="http://jacobmumm.com">Jacob Mumm</a> and
|
||||||
|
<a href="http://blog.igorminar.com">Igor Minar</a>
|
||||||
|
</p>
|
||||||
|
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
window.requestAnimationFrame = window.webkitRequestAnimationFrame = function(callback) {
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="node_modules/angular/angular.js"></script>
|
||||||
|
<script src="build.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
113
bench/implementations/angular-1.5.8/js/app.js
Normal file
113
bench/implementations/angular-1.5.8/js/app.js
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
/* jshint undef: true, unused: true */
|
||||||
|
/*global angular */
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
angular.module('todoCtrl', [])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main controller for the app. The controller:
|
||||||
|
* - retrieves and persists the model via the todoStorage service
|
||||||
|
* - exposes the model to the template and provides event handlers
|
||||||
|
*/
|
||||||
|
.controller('TodoCtrl', function TodoCtrl($scope, $location) {
|
||||||
|
var TC = this;
|
||||||
|
var todos = TC.todos = [];
|
||||||
|
|
||||||
|
TC.ESCAPE_KEY = 27;
|
||||||
|
TC.editedTodo = {};
|
||||||
|
|
||||||
|
function resetTodo() {
|
||||||
|
TC.newTodo = {title: '', completed: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTodo();
|
||||||
|
|
||||||
|
if ($location.path() === '') {
|
||||||
|
$location.path('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
TC.location = $location;
|
||||||
|
|
||||||
|
$scope.$watch('TC.location.path()', function (path) {
|
||||||
|
TC.statusFilter = { '/active': {completed: false}, '/completed': {completed: true} }[path];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3rd argument `true` for deep object watching
|
||||||
|
$scope.$watch('TC.todos', function () {
|
||||||
|
TC.remainingCount = todos.filter(function (todo) { return !todo.completed; }).length;
|
||||||
|
TC.allChecked = (TC.remainingCount === 0);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
TC.addTodo = function () {
|
||||||
|
var newTitle = TC.newTodo.title = TC.newTodo.title.trim();
|
||||||
|
if (newTitle.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
todos.push(TC.newTodo);
|
||||||
|
resetTodo();
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.editTodo = function (todo) {
|
||||||
|
TC.editedTodo = todo;
|
||||||
|
|
||||||
|
// Clone the original todo to restore it on demand.
|
||||||
|
TC.originalTodo = angular.copy(todo);
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.doneEditing = function (todo, index) {
|
||||||
|
TC.editedTodo = {};
|
||||||
|
todo.title = todo.title.trim();
|
||||||
|
|
||||||
|
if (!todo.title) {
|
||||||
|
TC.removeTodo(index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.revertEditing = function (index) {
|
||||||
|
TC.editedTodo = {};
|
||||||
|
todos[index] = TC.originalTodo;
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.removeTodo = function (index) {
|
||||||
|
todos.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.clearCompletedTodos = function () {
|
||||||
|
TC.todos = todos = todos.filter(function (val) {
|
||||||
|
return !val.completed;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
TC.markAll = function (completed) {
|
||||||
|
todos.forEach(function (todo) {
|
||||||
|
todo.completed = completed;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
angular.module('todoFocus', [])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directive that places focus on the element it is applied to when the expression it binds to evaluates to true
|
||||||
|
*/
|
||||||
|
.directive('todoFocus', function ($timeout) {
|
||||||
|
return function (scope, elem, attrs) {
|
||||||
|
scope.$watch(attrs.todoFocus, function (newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
$timeout(function () {
|
||||||
|
elem[0].focus();
|
||||||
|
}, 0, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* The main TodoMVC app module that pulls all dependency modules declared in same named files
|
||||||
|
*
|
||||||
|
* @type {angular.Module}
|
||||||
|
*/
|
||||||
|
angular.module('todomvc', ['todoCtrl', 'todoFocus']);
|
||||||
|
})();
|
||||||
14
bench/implementations/angular-1.5.8/package.json
Normal file
14
bench/implementations/angular-1.5.8/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"make": "uglifyjs js/app.js -o build.min.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"angular": "^1.5.8",
|
||||||
|
"todomvc-app-css": "^2.0.6",
|
||||||
|
"todomvc-common": "^1.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"uglify-js": "^2.6.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
bench/implementations/angular-1.5.8/readme.md
Normal file
19
bench/implementations/angular-1.5.8/readme.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Angular TodoMVC for Benchmarking
|
||||||
|
|
||||||
|
This is an update of the
|
||||||
|
_[Angular TodoMVC example](https://github.com/tastejs/todomvc/tree/gh-pages/examples/angularjs-perf)_
|
||||||
|
with the following changes:
|
||||||
|
|
||||||
|
* Upgrade to Angular 1.5.7
|
||||||
|
* Update todomvc-app-css to 2.0.6 to match other examples in this benchmark
|
||||||
|
* Remove localStorage functionality - not relevant to measuring render performance
|
||||||
|
* Concat JS into one file
|
||||||
|
* Minify with uglify
|
||||||
|
|
||||||
|
This example is listed as "performance optimized" in the TodoMVC repository, but doesn't appear to make any performance improvements to the basic example other than excluding all features not mentioned in the app specification. Therefore, it should count as having no optimizations for the purposes of this benchmark.
|
||||||
|
|
||||||
|
# Building
|
||||||
|
|
||||||
|
1. `npm install`
|
||||||
|
2. `npm run make`
|
||||||
|
3. Open a local server (e.g. with `npm install -g http-server`) and open index.html
|
||||||
28
bench/implementations/angular-2-optimized/.gitignore
vendored
Normal file
28
bench/implementations/angular-2-optimized/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
node_modules/.bin
|
||||||
|
|
||||||
|
# ignore the source / documentation of each node module, but retain the
|
||||||
|
# distribution builds
|
||||||
|
|
||||||
|
node_modules/systemjs
|
||||||
|
!node_modules/systemjs/dist/system.src.js
|
||||||
|
|
||||||
|
node_modules/todomvc-app-css
|
||||||
|
!node_modules/todomvc-app-css/index.css
|
||||||
|
|
||||||
|
node_modules/todomvc-common
|
||||||
|
!node_modules/todomvc-common/base.css
|
||||||
|
|
||||||
|
node_modules/angular2
|
||||||
|
!node_modules/angular2/bundles/angular2-polyfills.js
|
||||||
|
!node_modules/angular2/bundles/angular2.dev.js
|
||||||
|
|
||||||
|
node_modules/rxjs
|
||||||
|
!node_modules/rxjs/bundles/Rx.js
|
||||||
|
|
||||||
|
# Ignore development dependencies
|
||||||
|
node_modules/tsd
|
||||||
|
node_modules/typescript
|
||||||
|
|
||||||
|
*.js
|
||||||
|
*.js.map
|
||||||
|
typings
|
||||||
23
bench/implementations/angular-2-optimized/app/app.html
Normal file
23
bench/implementations/angular-2-optimized/app/app.html
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<section class="todoapp">
|
||||||
|
<header class="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodoText" (keydown.enter)="addTodo()">
|
||||||
|
</header>
|
||||||
|
<section class="main" *ngIf="todoStore.todos.length > 0">
|
||||||
|
<input class="toggle-all" type="checkbox" *ngIf="todoStore.todos.length" #toggleall [checked]="todoStore.allCompleted()" (click)="todoStore.setAllTo(toggleall.checked)">
|
||||||
|
<ul class="todo-list">
|
||||||
|
<li *ngFor="#todo of todoStore.todos; trackBy:identify" [class.completed]="todo.completed" [class.editing]="todo.editing">
|
||||||
|
<div class="view">
|
||||||
|
<input class="toggle" type="checkbox" (click)="toggleCompletion(todo)" [checked]="todo.completed">
|
||||||
|
<label (dblclick)="editTodo(todo)">{{todo.title}}</label>
|
||||||
|
<button class="destroy" (click)="remove(todo)"></button>
|
||||||
|
</div>
|
||||||
|
<input class="edit" *ngIf="todo.editing" [value]="todo.title" #editedtodo (blur)="stopEditing(todo, editedtodo.value)" (keydown.enter)="updateEditingTodo(todo, editedtodo.value)" (keydown.escape)="cancelEditingTodo(todo)">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<footer class="footer" *ngIf="todoStore.todos.length > 0">
|
||||||
|
<span class="todo-count"><strong>{{todoStore.getRemaining().length}}</strong> {{todoStore.getRemaining().length == 1 ? 'item' : 'items'}} left</span>
|
||||||
|
<button class="clear-completed" *ngIf="todoStore.getCompleted().length > 0" (click)="removeCompleted()">Clear completed</button>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
62
bench/implementations/angular-2-optimized/app/app.ts
Normal file
62
bench/implementations/angular-2-optimized/app/app.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import {Component} from 'angular2/core';
|
||||||
|
import {TodoStore, Todo} from './services/store';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'todo-app',
|
||||||
|
templateUrl: 'app/app.html'
|
||||||
|
})
|
||||||
|
export default class TodoApp {
|
||||||
|
todoStore: TodoStore;
|
||||||
|
newTodoText = '';
|
||||||
|
|
||||||
|
constructor(todoStore: TodoStore) {
|
||||||
|
this.todoStore = todoStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
identify(index: number) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopEditing(todo: Todo, editedTitle: string) {
|
||||||
|
todo.title = editedTitle;
|
||||||
|
todo.editing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEditingTodo(todo: Todo) {
|
||||||
|
todo.editing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEditingTodo(todo: Todo, editedTitle: string) {
|
||||||
|
editedTitle = editedTitle.trim();
|
||||||
|
todo.editing = false;
|
||||||
|
|
||||||
|
if (editedTitle.length === 0) {
|
||||||
|
return this.todoStore.remove(todo);
|
||||||
|
}
|
||||||
|
|
||||||
|
todo.title = editedTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
editTodo(todo: Todo) {
|
||||||
|
todo.editing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCompleted() {
|
||||||
|
this.todoStore.removeCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCompletion(todo: Todo) {
|
||||||
|
this.todoStore.toggleCompletion(todo);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(todo: Todo){
|
||||||
|
this.todoStore.remove(todo);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTodo() {
|
||||||
|
if (this.newTodoText.trim().length) {
|
||||||
|
this.todoStore.add(this.newTodoText);
|
||||||
|
this.newTodoText = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
///<reference path="../node_modules/angular2/typings/browser.d.ts"/>
|
||||||
|
|
||||||
|
import {bootstrap} from 'angular2/platform/browser';
|
||||||
|
import TodoApp from './app'
|
||||||
|
import {TodoStore} from './services/store';
|
||||||
|
import {enableProdMode} from 'angular2/core';
|
||||||
|
|
||||||
|
enableProdMode();
|
||||||
|
bootstrap(TodoApp, [TodoStore]);
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
export class Todo {
|
||||||
|
completed: Boolean;
|
||||||
|
editing: Boolean;
|
||||||
|
|
||||||
|
private _title: String;
|
||||||
|
get title() {
|
||||||
|
return this._title;
|
||||||
|
}
|
||||||
|
set title(value: String) {
|
||||||
|
this._title = value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(title: String) {
|
||||||
|
this.completed = false;
|
||||||
|
this.editing = false;
|
||||||
|
this.title = title.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoStore {
|
||||||
|
todos: Array<Todo>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.todos = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWithCompleted(completed: Boolean) {
|
||||||
|
return this.todos.filter((todo: Todo) => todo.completed === completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
allCompleted() {
|
||||||
|
return this.todos.length === this.getCompleted().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAllTo(completed: Boolean) {
|
||||||
|
this.todos.forEach((t: Todo) => t.completed = completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCompleted() {
|
||||||
|
this.todos = this.getWithCompleted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRemaining() {
|
||||||
|
return this.getWithCompleted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompleted() {
|
||||||
|
return this.getWithCompleted(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCompletion(todo: Todo) {
|
||||||
|
todo.completed = !todo.completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(todo: Todo) {
|
||||||
|
this.todos.splice(this.todos.indexOf(todo), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(title: String) {
|
||||||
|
this.todos.push(new Todo(title));
|
||||||
|
}
|
||||||
|
}
|
||||||
35
bench/implementations/angular-2-optimized/index.html
Normal file
35
bench/implementations/angular-2-optimized/index.html
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" data-framework="angular2">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Angular2 • TodoMVC</title>
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
|
||||||
|
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||||
|
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||||
|
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||||
|
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<todo-app></todo-app>
|
||||||
|
<footer class="info">
|
||||||
|
<p>Double-click to edit a todo</p>
|
||||||
|
<p>
|
||||||
|
Created by <a href="http://github.com/samccone">Sam Saccone</a> and <a href="http://github.com/colineberhardt">Colin Eberhardt</a>
|
||||||
|
using <a href="http://angular.io">Angular2</a>
|
||||||
|
</p>
|
||||||
|
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
System.config({
|
||||||
|
packages: {
|
||||||
|
'app': {
|
||||||
|
format: 'cjs',
|
||||||
|
defaultExtension: 'js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
System.import('app/bootstrap');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
bench/implementations/angular-2-optimized/package.json
Normal file
19
bench/implementations/angular-2-optimized/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^1.8.10"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"angular2": "^2.0.0-beta.17",
|
||||||
|
"es6-shim": "^0.35.1",
|
||||||
|
"reflect-metadata": "0.1.2",
|
||||||
|
"rxjs": "5.0.0-beta.6",
|
||||||
|
"systemjs": "0.19.6",
|
||||||
|
"todomvc-app-css": "^2.0.0",
|
||||||
|
"todomvc-common": "^1.0.1",
|
||||||
|
"zone.js": "^0.6.17"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
bench/implementations/angular-2-optimized/readme.md
Normal file
32
bench/implementations/angular-2-optimized/readme.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Angular 2 • [TodoMVC](http://todomvc.com)
|
||||||
|
|
||||||
|
> Angular is a development platform for creating applications using modern web standards. Angular includes a wealth of essential features such as mobile gestures, animations, filtering, routing, data binding, security, internationalization, and beautiful UI components. It's extremely modular, lightweight, and easy to learn.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Website](https://angular.io/)
|
||||||
|
- [Documentation](https://angular.io/docs/ts/latest/)
|
||||||
|
|
||||||
|
### Articles
|
||||||
|
|
||||||
|
- [Angular 2 Beta Announcement](http://angularjs.blogspot.co.uk/2015/12/angular-2-beta.html)
|
||||||
|
|
||||||
|
### Support
|
||||||
|
|
||||||
|
- [StackOverflow](http://stackoverflow.com/questions/tagged/angular2)
|
||||||
|
- [Google Groups](https://groups.google.com/forum/#!forum/angular)
|
||||||
|
- [Twitter](http://twitter.com/angularjs)
|
||||||
|
- [Google+](https://plus.sandbox.google.com/+AngularJS/posts)
|
||||||
|
|
||||||
|
*Let us [know](https://github.com/tastejs/todomvc/issues) if you discover anything worth sharing.*
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
This app was built using TypeScript and Angular 2 beta. To make changes simply
|
||||||
|
|
||||||
|
* `npm i`
|
||||||
|
* `npm run dev`
|
||||||
|
|
||||||
|
## Credit
|
||||||
|
|
||||||
|
Created by [Sam Saccone](http://github.com/samccone)
|
||||||
15
bench/implementations/angular-2-optimized/tsconfig.json
Normal file
15
bench/implementations/angular-2-optimized/tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES5",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": false,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"noImplicitAny": true
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"app/bootstrap.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
28
bench/implementations/angular-2/.gitignore
vendored
Executable file
28
bench/implementations/angular-2/.gitignore
vendored
Executable file
|
|
@ -0,0 +1,28 @@
|
||||||
|
node_modules/.bin
|
||||||
|
|
||||||
|
# ignore the source / documentation of each node module, but retain the
|
||||||
|
# distribution builds
|
||||||
|
|
||||||
|
node_modules/systemjs
|
||||||
|
!node_modules/systemjs/dist/system.src.js
|
||||||
|
|
||||||
|
node_modules/todomvc-app-css
|
||||||
|
!node_modules/todomvc-app-css/index.css
|
||||||
|
|
||||||
|
node_modules/todomvc-common
|
||||||
|
!node_modules/todomvc-common/base.css
|
||||||
|
|
||||||
|
node_modules/angular2
|
||||||
|
!node_modules/angular2/bundles/angular2-polyfills.js
|
||||||
|
!node_modules/angular2/bundles/angular2.dev.js
|
||||||
|
|
||||||
|
node_modules/rxjs
|
||||||
|
!node_modules/rxjs/bundles/Rx.js
|
||||||
|
|
||||||
|
# Ignore development dependencies
|
||||||
|
node_modules/tsd
|
||||||
|
node_modules/typescript
|
||||||
|
|
||||||
|
*.js
|
||||||
|
*.js.map
|
||||||
|
typings
|
||||||
23
bench/implementations/angular-2/app/app.html
Executable file
23
bench/implementations/angular-2/app/app.html
Executable file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<section class="todoapp">
|
||||||
|
<header class="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodoText" (keydown.enter)="addTodo()">
|
||||||
|
</header>
|
||||||
|
<section class="main" *ngIf="todoStore.todos.length > 0">
|
||||||
|
<input class="toggle-all" type="checkbox" *ngIf="todoStore.todos.length" #toggleall [checked]="todoStore.allCompleted()" (click)="todoStore.setAllTo(toggleall.checked)">
|
||||||
|
<ul class="todo-list">
|
||||||
|
<li *ngFor="#todo of todoStore.todos" [class.completed]="todo.completed" [class.editing]="todo.editing">
|
||||||
|
<div class="view">
|
||||||
|
<input class="toggle" type="checkbox" (click)="toggleCompletion(todo)" [checked]="todo.completed">
|
||||||
|
<label (dblclick)="editTodo(todo)">{{todo.title}}</label>
|
||||||
|
<button class="destroy" (click)="remove(todo)"></button>
|
||||||
|
</div>
|
||||||
|
<input class="edit" *ngIf="todo.editing" [value]="todo.title" #editedtodo (blur)="stopEditing(todo, editedtodo.value)" (keydown.enter)="updateEditingTodo(todo, editedtodo.value)" (keydown.escape)="cancelEditingTodo(todo)">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<footer class="footer" *ngIf="todoStore.todos.length > 0">
|
||||||
|
<span class="todo-count"><strong>{{todoStore.getRemaining().length}}</strong> {{todoStore.getRemaining().length == 1 ? 'item' : 'items'}} left</span>
|
||||||
|
<button class="clear-completed" *ngIf="todoStore.getCompleted().length > 0" (click)="removeCompleted()">Clear completed</button>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
58
bench/implementations/angular-2/app/app.ts
Executable file
58
bench/implementations/angular-2/app/app.ts
Executable file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {Component} from 'angular2/core';
|
||||||
|
import {TodoStore, Todo} from './services/store';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'todo-app',
|
||||||
|
templateUrl: 'app/app.html'
|
||||||
|
})
|
||||||
|
export default class TodoApp {
|
||||||
|
todoStore: TodoStore;
|
||||||
|
newTodoText = '';
|
||||||
|
|
||||||
|
constructor(todoStore: TodoStore) {
|
||||||
|
this.todoStore = todoStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopEditing(todo: Todo, editedTitle: string) {
|
||||||
|
todo.title = editedTitle;
|
||||||
|
todo.editing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEditingTodo(todo: Todo) {
|
||||||
|
todo.editing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEditingTodo(todo: Todo, editedTitle: string) {
|
||||||
|
editedTitle = editedTitle.trim();
|
||||||
|
todo.editing = false;
|
||||||
|
|
||||||
|
if (editedTitle.length === 0) {
|
||||||
|
return this.todoStore.remove(todo);
|
||||||
|
}
|
||||||
|
|
||||||
|
todo.title = editedTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
editTodo(todo: Todo) {
|
||||||
|
todo.editing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCompleted() {
|
||||||
|
this.todoStore.removeCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCompletion(todo: Todo) {
|
||||||
|
this.todoStore.toggleCompletion(todo);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(todo: Todo){
|
||||||
|
this.todoStore.remove(todo);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTodo() {
|
||||||
|
if (this.newTodoText.trim().length) {
|
||||||
|
this.todoStore.add(this.newTodoText);
|
||||||
|
this.newTodoText = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
bench/implementations/angular-2/app/bootstrap.ts
Executable file
9
bench/implementations/angular-2/app/bootstrap.ts
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
///<reference path="../node_modules/angular2/typings/browser.d.ts"/>
|
||||||
|
|
||||||
|
import {bootstrap} from 'angular2/platform/browser';
|
||||||
|
import TodoApp from './app'
|
||||||
|
import {TodoStore} from './services/store';
|
||||||
|
import {enableProdMode} from 'angular2/core';
|
||||||
|
|
||||||
|
enableProdMode();
|
||||||
|
bootstrap(TodoApp, [TodoStore]);
|
||||||
62
bench/implementations/angular-2/app/services/store.ts
Executable file
62
bench/implementations/angular-2/app/services/store.ts
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
export class Todo {
|
||||||
|
completed: Boolean;
|
||||||
|
editing: Boolean;
|
||||||
|
|
||||||
|
private _title: String;
|
||||||
|
get title() {
|
||||||
|
return this._title;
|
||||||
|
}
|
||||||
|
set title(value: String) {
|
||||||
|
this._title = value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(title: String) {
|
||||||
|
this.completed = false;
|
||||||
|
this.editing = false;
|
||||||
|
this.title = title.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TodoStore {
|
||||||
|
todos: Array<Todo>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.todos = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWithCompleted(completed: Boolean) {
|
||||||
|
return this.todos.filter((todo: Todo) => todo.completed === completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
allCompleted() {
|
||||||
|
return this.todos.length === this.getCompleted().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAllTo(completed: Boolean) {
|
||||||
|
this.todos.forEach((t: Todo) => t.completed = completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCompleted() {
|
||||||
|
this.todos = this.getWithCompleted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRemaining() {
|
||||||
|
return this.getWithCompleted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompleted() {
|
||||||
|
return this.getWithCompleted(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCompletion(todo: Todo) {
|
||||||
|
todo.completed = !todo.completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(todo: Todo) {
|
||||||
|
this.todos.splice(this.todos.indexOf(todo), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(title: String) {
|
||||||
|
this.todos.push(new Todo(title));
|
||||||
|
}
|
||||||
|
}
|
||||||
35
bench/implementations/angular-2/index.html
Executable file
35
bench/implementations/angular-2/index.html
Executable file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" data-framework="angular2">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Angular2 • TodoMVC</title>
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
|
||||||
|
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||||
|
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||||
|
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||||
|
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<todo-app></todo-app>
|
||||||
|
<footer class="info">
|
||||||
|
<p>Double-click to edit a todo</p>
|
||||||
|
<p>
|
||||||
|
Created by <a href="http://github.com/samccone">Sam Saccone</a> and <a href="http://github.com/colineberhardt">Colin Eberhardt</a>
|
||||||
|
using <a href="http://angular.io">Angular2</a>
|
||||||
|
</p>
|
||||||
|
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
System.config({
|
||||||
|
packages: {
|
||||||
|
'app': {
|
||||||
|
format: 'cjs',
|
||||||
|
defaultExtension: 'js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
System.import('app/bootstrap');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
bench/implementations/angular-2/package.json
Executable file
19
bench/implementations/angular-2/package.json
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^1.8.10"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"angular2": "^2.0.0-beta.17",
|
||||||
|
"es6-shim": "^0.35.1",
|
||||||
|
"reflect-metadata": "0.1.2",
|
||||||
|
"rxjs": "5.0.0-beta.6",
|
||||||
|
"systemjs": "0.19.6",
|
||||||
|
"todomvc-app-css": "^2.0.0",
|
||||||
|
"todomvc-common": "^1.0.1",
|
||||||
|
"zone.js": "^0.6.17"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
bench/implementations/angular-2/readme.md
Executable file
32
bench/implementations/angular-2/readme.md
Executable file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Angular 2 • [TodoMVC](http://todomvc.com)
|
||||||
|
|
||||||
|
> Angular is a development platform for creating applications using modern web standards. Angular includes a wealth of essential features such as mobile gestures, animations, filtering, routing, data binding, security, internationalization, and beautiful UI components. It's extremely modular, lightweight, and easy to learn.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Website](https://angular.io/)
|
||||||
|
- [Documentation](https://angular.io/docs/ts/latest/)
|
||||||
|
|
||||||
|
### Articles
|
||||||
|
|
||||||
|
- [Angular 2 Beta Announcement](http://angularjs.blogspot.co.uk/2015/12/angular-2-beta.html)
|
||||||
|
|
||||||
|
### Support
|
||||||
|
|
||||||
|
- [StackOverflow](http://stackoverflow.com/questions/tagged/angular2)
|
||||||
|
- [Google Groups](https://groups.google.com/forum/#!forum/angular)
|
||||||
|
- [Twitter](http://twitter.com/angularjs)
|
||||||
|
- [Google+](https://plus.sandbox.google.com/+AngularJS/posts)
|
||||||
|
|
||||||
|
*Let us [know](https://github.com/tastejs/todomvc/issues) if you discover anything worth sharing.*
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
This app was built using TypeScript and Angular 2 beta. To make changes simply
|
||||||
|
|
||||||
|
* `npm i`
|
||||||
|
* `npm run dev`
|
||||||
|
|
||||||
|
## Credit
|
||||||
|
|
||||||
|
Created by [Sam Saccone](http://github.com/samccone)
|
||||||
15
bench/implementations/angular-2/tsconfig.json
Executable file
15
bench/implementations/angular-2/tsconfig.json
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES5",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": false,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"noImplicitAny": true
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"app/bootstrap.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
339
bench/implementations/elm-0.16-optimized/Todo.elm
Executable file
339
bench/implementations/elm-0.16-optimized/Todo.elm
Executable file
|
|
@ -0,0 +1,339 @@
|
||||||
|
module Todo where
|
||||||
|
{-| TodoMVC implemented in Elm, using plain HTML and CSS for rendering.
|
||||||
|
|
||||||
|
This application is broken up into four distinct parts:
|
||||||
|
|
||||||
|
1. Model - a full definition of the application's state
|
||||||
|
2. Update - a way to step the application state forward
|
||||||
|
3. View - a way to visualize our application state with HTML
|
||||||
|
4. Inputs - the signals necessary to manage events
|
||||||
|
|
||||||
|
This clean division of concerns is a core part of Elm. You can read more about
|
||||||
|
this in the Pong tutorial: http://elm-lang.org/blog/making-pong
|
||||||
|
|
||||||
|
This program is not particularly large, so definitely see the following
|
||||||
|
for notes on structuring more complex GUIs with Elm:
|
||||||
|
https://github.com/evancz/elm-architecture-tutorial/
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Html.Events exposing (..)
|
||||||
|
import Html.Lazy exposing (lazy2, lazy3)
|
||||||
|
import Json.Decode as Json
|
||||||
|
import Signal exposing (Signal, Address)
|
||||||
|
import String
|
||||||
|
import Window
|
||||||
|
|
||||||
|
|
||||||
|
---- MODEL ----
|
||||||
|
|
||||||
|
-- The full application state of our todo app.
|
||||||
|
type alias Model =
|
||||||
|
{ tasks : List Task
|
||||||
|
, field : String
|
||||||
|
, uid : Int
|
||||||
|
, visibility : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Task =
|
||||||
|
{ description : String
|
||||||
|
, completed : Bool
|
||||||
|
, editing : Bool
|
||||||
|
, id : Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
newTask : String -> Int -> Task
|
||||||
|
newTask desc id =
|
||||||
|
{ description = desc
|
||||||
|
, completed = False
|
||||||
|
, editing = False
|
||||||
|
, id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
emptyModel : Model
|
||||||
|
emptyModel =
|
||||||
|
{ tasks = []
|
||||||
|
, visibility = "All"
|
||||||
|
, field = ""
|
||||||
|
, uid = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
---- UPDATE ----
|
||||||
|
|
||||||
|
-- A description of the kinds of actions that can be performed on the model of
|
||||||
|
-- our application. See the following for more info on this pattern and
|
||||||
|
-- some alternatives: https://github.com/evancz/elm-architecture-tutorial/
|
||||||
|
type Action
|
||||||
|
= NoOp
|
||||||
|
| UpdateField String
|
||||||
|
| EditingTask Int Bool
|
||||||
|
| UpdateTask Int String
|
||||||
|
| Add
|
||||||
|
| Delete Int
|
||||||
|
| DeleteComplete
|
||||||
|
| Check Int Bool
|
||||||
|
| CheckAll Bool
|
||||||
|
| ChangeVisibility String
|
||||||
|
|
||||||
|
|
||||||
|
-- How we update our Model on a given Action?
|
||||||
|
update : Action -> Model -> Model
|
||||||
|
update action model =
|
||||||
|
case action of
|
||||||
|
NoOp -> model
|
||||||
|
|
||||||
|
Add ->
|
||||||
|
{ model |
|
||||||
|
uid = model.uid + 1,
|
||||||
|
field = "",
|
||||||
|
tasks =
|
||||||
|
if String.isEmpty model.field
|
||||||
|
then model.tasks
|
||||||
|
else model.tasks ++ [newTask model.field model.uid]
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateField str ->
|
||||||
|
{ model | field = str }
|
||||||
|
|
||||||
|
EditingTask id isEditing ->
|
||||||
|
let updateTask t = if t.id == id then { t | editing = isEditing } else t
|
||||||
|
in
|
||||||
|
{ model | tasks = List.map updateTask model.tasks }
|
||||||
|
|
||||||
|
UpdateTask id task ->
|
||||||
|
let updateTask t = if t.id == id then { t | description = task } else t
|
||||||
|
in
|
||||||
|
{ model | tasks = List.map updateTask model.tasks }
|
||||||
|
|
||||||
|
Delete id ->
|
||||||
|
{ model | tasks = List.filter (\t -> t.id /= id) model.tasks }
|
||||||
|
|
||||||
|
DeleteComplete ->
|
||||||
|
{ model | tasks = List.filter (not << .completed) model.tasks }
|
||||||
|
|
||||||
|
Check id isCompleted ->
|
||||||
|
let updateTask t = if t.id == id then { t | completed = isCompleted } else t
|
||||||
|
in
|
||||||
|
{ model | tasks = List.map updateTask model.tasks }
|
||||||
|
|
||||||
|
CheckAll isCompleted ->
|
||||||
|
let updateTask t = { t | completed = isCompleted }
|
||||||
|
in
|
||||||
|
{ model | tasks = List.map updateTask model.tasks }
|
||||||
|
|
||||||
|
ChangeVisibility visibility ->
|
||||||
|
{ model | visibility = visibility }
|
||||||
|
|
||||||
|
|
||||||
|
---- VIEW ----
|
||||||
|
|
||||||
|
view : Address Action -> Model -> Html
|
||||||
|
view address model =
|
||||||
|
div
|
||||||
|
[ class "todomvc-wrapper"
|
||||||
|
, style [ ("visibility", "hidden") ]
|
||||||
|
]
|
||||||
|
[ section
|
||||||
|
[ class "todoapp" ]
|
||||||
|
[ lazy2 taskEntry address model.field
|
||||||
|
, lazy3 taskList address model.visibility model.tasks
|
||||||
|
, lazy3 controls address model.visibility model.tasks
|
||||||
|
]
|
||||||
|
, infoFooter
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
onEnter : Address a -> a -> Attribute
|
||||||
|
onEnter address value =
|
||||||
|
on "keydown"
|
||||||
|
(Json.customDecoder keyCode is13)
|
||||||
|
(\_ -> Signal.message address value)
|
||||||
|
|
||||||
|
|
||||||
|
is13 : Int -> Result String ()
|
||||||
|
is13 code =
|
||||||
|
if code == 13 then Ok () else Err "not the right key code"
|
||||||
|
|
||||||
|
|
||||||
|
taskEntry : Address Action -> String -> Html
|
||||||
|
taskEntry address task =
|
||||||
|
header
|
||||||
|
[ class "header" ]
|
||||||
|
[ h1 [] [ text "todos" ]
|
||||||
|
, input
|
||||||
|
[ class "new-todo"
|
||||||
|
, placeholder "What needs to be done?"
|
||||||
|
, autofocus True
|
||||||
|
, value task
|
||||||
|
, name "newTodo"
|
||||||
|
, on "input" targetValue (Signal.message address << UpdateField)
|
||||||
|
, onEnter address Add
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
taskList : Address Action -> String -> List Task -> Html
|
||||||
|
taskList address visibility tasks =
|
||||||
|
let isVisible todo =
|
||||||
|
case visibility of
|
||||||
|
"Completed" -> todo.completed
|
||||||
|
"Active" -> not todo.completed
|
||||||
|
_ -> True
|
||||||
|
|
||||||
|
allCompleted = List.all .completed tasks
|
||||||
|
|
||||||
|
cssVisibility = if List.isEmpty tasks then "hidden" else "visible"
|
||||||
|
in
|
||||||
|
section
|
||||||
|
[ class "main"
|
||||||
|
, style [ ("visibility", cssVisibility) ]
|
||||||
|
]
|
||||||
|
[ input
|
||||||
|
[ class "toggle-all"
|
||||||
|
, type' "checkbox"
|
||||||
|
, name "toggle"
|
||||||
|
, checked allCompleted
|
||||||
|
, onClick address (CheckAll (not allCompleted))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label
|
||||||
|
[ for "toggle-all" ]
|
||||||
|
[ text "Mark all as complete" ]
|
||||||
|
, ul
|
||||||
|
[ class "todo-list" ]
|
||||||
|
(List.map (todoItem address) (List.filter isVisible tasks))
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
todoItem : Address Action -> Task -> Html
|
||||||
|
todoItem address todo =
|
||||||
|
li
|
||||||
|
[ classList [ ("completed", todo.completed), ("editing", todo.editing) ]
|
||||||
|
, key (toString todo.id)
|
||||||
|
]
|
||||||
|
[ div
|
||||||
|
[ class "view" ]
|
||||||
|
[ input
|
||||||
|
[ class "toggle"
|
||||||
|
, type' "checkbox"
|
||||||
|
, checked todo.completed
|
||||||
|
, onClick address (Check todo.id (not todo.completed))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label
|
||||||
|
[ onDoubleClick address (EditingTask todo.id True) ]
|
||||||
|
[ text todo.description ]
|
||||||
|
, button
|
||||||
|
[ class "destroy"
|
||||||
|
, onClick address (Delete todo.id)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ class "edit"
|
||||||
|
, value todo.description
|
||||||
|
, name "title"
|
||||||
|
, id ("todo-" ++ toString todo.id)
|
||||||
|
, on "input" targetValue (Signal.message address << UpdateTask todo.id)
|
||||||
|
, onBlur address (EditingTask todo.id False)
|
||||||
|
, onEnter address (EditingTask todo.id False)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
controls : Address Action -> String -> List Task -> Html
|
||||||
|
controls address visibility tasks =
|
||||||
|
let tasksCompleted = List.length (List.filter .completed tasks)
|
||||||
|
tasksLeft = List.length tasks - tasksCompleted
|
||||||
|
item_ = if tasksLeft == 1 then " item" else " items"
|
||||||
|
in
|
||||||
|
footer
|
||||||
|
[ class "footer"
|
||||||
|
, hidden (List.isEmpty tasks)
|
||||||
|
]
|
||||||
|
[ span
|
||||||
|
[ class "todo-count" ]
|
||||||
|
[ strong [] [ text (toString tasksLeft) ]
|
||||||
|
, text (item_ ++ " left")
|
||||||
|
]
|
||||||
|
, ul
|
||||||
|
[ class "filters" ]
|
||||||
|
[ visibilitySwap address "#/" "All" visibility
|
||||||
|
, text " "
|
||||||
|
, visibilitySwap address "#/active" "Active" visibility
|
||||||
|
, text " "
|
||||||
|
, visibilitySwap address "#/completed" "Completed" visibility
|
||||||
|
]
|
||||||
|
, button
|
||||||
|
[ class "clear-completed"
|
||||||
|
, hidden (tasksCompleted == 0)
|
||||||
|
, onClick address DeleteComplete
|
||||||
|
]
|
||||||
|
[ text ("Clear completed (" ++ toString tasksCompleted ++ ")") ]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
visibilitySwap : Address Action -> String -> String -> String -> Html
|
||||||
|
visibilitySwap address uri visibility actualVisibility =
|
||||||
|
li
|
||||||
|
[ onClick address (ChangeVisibility visibility) ]
|
||||||
|
[ a [ href uri, classList [("selected", visibility == actualVisibility)] ] [ text visibility ] ]
|
||||||
|
|
||||||
|
|
||||||
|
infoFooter : Html
|
||||||
|
infoFooter =
|
||||||
|
footer [ class "info" ]
|
||||||
|
[ p [] [ text "Double-click to edit a todo" ]
|
||||||
|
, p []
|
||||||
|
[ text "Written by "
|
||||||
|
, a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ]
|
||||||
|
]
|
||||||
|
, p []
|
||||||
|
[ text "Part of "
|
||||||
|
, a [ href "http://todomvc.com" ] [ text "TodoMVC" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
---- INPUTS ----
|
||||||
|
|
||||||
|
-- wire the entire application together
|
||||||
|
main : Signal Html
|
||||||
|
main =
|
||||||
|
Signal.map (view actions.address) model
|
||||||
|
|
||||||
|
|
||||||
|
-- manage the model of our application over time
|
||||||
|
model : Signal Model
|
||||||
|
model =
|
||||||
|
Signal.foldp update emptyModel actions.signal
|
||||||
|
|
||||||
|
|
||||||
|
-- actions from user input
|
||||||
|
actions : Signal.Mailbox Action
|
||||||
|
actions =
|
||||||
|
Signal.mailbox NoOp
|
||||||
|
|
||||||
|
|
||||||
|
port focus : Signal String
|
||||||
|
port focus =
|
||||||
|
let needsFocus act =
|
||||||
|
case act of
|
||||||
|
EditingTask id bool -> bool
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
toSelector act =
|
||||||
|
case act of
|
||||||
|
EditingTask id _ -> "#todo-" ++ toString id
|
||||||
|
_ -> ""
|
||||||
|
in
|
||||||
|
actions.signal
|
||||||
|
|> Signal.filter needsFocus (EditingTask 0 True)
|
||||||
|
|> Signal.map toSelector
|
||||||
15
bench/implementations/elm-0.16-optimized/elm-package.json
Normal file
15
bench/implementations/elm-0.16-optimized/elm-package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"summary": "TodoMVC created with Elm and elm-html",
|
||||||
|
"repository": "https://github.com/evancz/elm-todomvc.git",
|
||||||
|
"license": "BSD3",
|
||||||
|
"source-directories": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"exposed-modules": [],
|
||||||
|
"dependencies": {
|
||||||
|
"elm-lang/core": "3.0.0 <= v < 4.0.0",
|
||||||
|
"evancz/elm-html": "4.0.2 <= v < 5.0.0"
|
||||||
|
},
|
||||||
|
"elm-version": "0.16.0 <= v < 0.17.0"
|
||||||
|
}
|
||||||
10560
bench/implementations/elm-0.16-optimized/elm.js
Executable file
10560
bench/implementations/elm-0.16-optimized/elm.js
Executable file
File diff suppressed because it is too large
Load diff
26
bench/implementations/elm-0.16-optimized/index.html
Normal file
26
bench/implementations/elm-0.16-optimized/index.html
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Elm • TodoMVC</title>
|
||||||
|
<script type="text/javascript" src="elm.js"></script>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var todomvc = Elm.fullscreen(Elm.Todo);
|
||||||
|
todomvc.ports.focus.subscribe(function(selector) {
|
||||||
|
setTimeout(function() {
|
||||||
|
var nodes = document.querySelectorAll(selector);
|
||||||
|
if (nodes.length === 1 && document.activeElement !== nodes[0]) {
|
||||||
|
nodes[0].focus();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
382
bench/implementations/elm-0.16-optimized/style.css
Normal file
382
bench/implementations/elm-0.16-optimized/style.css
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todomvc-wrapper {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
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; /* Mobile Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto 0;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
-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-line;
|
||||||
|
word-break: break-all;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hack to remove background from Mobile Safari.
|
||||||
|
Can't use it globally since it destroys checkboxes in Firefox
|
||||||
|
*/
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
336
bench/implementations/elm-0.16/Todo.elm
Normal file
336
bench/implementations/elm-0.16/Todo.elm
Normal file
|
|
@ -0,0 +1,336 @@
|
||||||
|
module Todo where
|
||||||
|
{-| TodoMVC implemented in Elm, using plain HTML and CSS for rendering.
|
||||||
|
|
||||||
|
This application is broken up into four distinct parts:
|
||||||
|
|
||||||
|
1. Model - a full definition of the application's state
|
||||||
|
2. Update - a way to step the application state forward
|
||||||
|
3. View - a way to visualize our application state with HTML
|
||||||
|
4. Inputs - the signals necessary to manage events
|
||||||
|
|
||||||
|
This clean division of concerns is a core part of Elm. You can read more about
|
||||||
|
this in the Pong tutorial: http://elm-lang.org/blog/making-pong
|
||||||
|
|
||||||
|
This program is not particularly large, so definitely see the following
|
||||||
|
for notes on structuring more complex GUIs with Elm:
|
||||||
|
https://github.com/evancz/elm-architecture-tutorial/
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Html.Events exposing (..)
|
||||||
|
import Json.Decode as Json
|
||||||
|
import Signal exposing (Signal, Address)
|
||||||
|
import String
|
||||||
|
import Window
|
||||||
|
|
||||||
|
|
||||||
|
---- MODEL ----
|
||||||
|
|
||||||
|
-- The full application state of our todo app.
|
||||||
|
type alias Model =
|
||||||
|
{ tasks : List Task
|
||||||
|
, field : String
|
||||||
|
, uid : Int
|
||||||
|
, visibility : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Task =
|
||||||
|
{ description : String
|
||||||
|
, completed : Bool
|
||||||
|
, editing : Bool
|
||||||
|
, id : Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
newTask : String -> Int -> Task
|
||||||
|
newTask desc id =
|
||||||
|
{ description = desc
|
||||||
|
, completed = False
|
||||||
|
, editing = False
|
||||||
|
, id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
emptyModel : Model
|
||||||
|
emptyModel =
|
||||||
|
{ tasks = []
|
||||||
|
, visibility = "All"
|
||||||
|
, field = ""
|
||||||
|
, uid = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
---- UPDATE ----
|
||||||
|
|
||||||
|
-- A description of the kinds of actions that can be performed on the model of
|
||||||
|
-- our application. See the following for more info on this pattern and
|
||||||
|
-- some alternatives: https://github.com/evancz/elm-architecture-tutorial/
|
||||||
|
type Action
|
||||||
|
= NoOp
|
||||||
|
| UpdateField String
|
||||||
|
| EditingTask Int Bool
|
||||||
|
| UpdateTask Int String
|
||||||
|
| Add
|
||||||
|
| Delete Int
|
||||||
|
| DeleteComplete
|
||||||
|
| Check Int Bool
|
||||||
|
| CheckAll Bool
|
||||||
|
| ChangeVisibility String
|
||||||
|
|
||||||
|
|
||||||
|
-- How we update our Model on a given Action?
|
||||||
|
update : Action -> Model -> Model
|
||||||
|
update action model =
|
||||||
|
case action of
|
||||||
|
NoOp -> model
|
||||||
|
|
||||||
|
Add ->
|
||||||
|
{ model |
|
||||||
|
uid = model.uid + 1,
|
||||||
|
field = "",
|
||||||
|
tasks =
|
||||||
|
if String.isEmpty model.field
|
||||||
|
then model.tasks
|
||||||
|
else model.tasks ++ [newTask model.field model.uid]
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateField str ->
|
||||||
|
{ model | field = str }
|
||||||
|
|
||||||
|
EditingTask id isEditing ->
|
||||||
|
let updateTask t = if t.id == id then { t | editing = isEditing } else t
|
||||||
|
in
|
||||||
|
{ model | tasks = List.map updateTask model.tasks }
|
||||||
|
|
||||||
|
UpdateTask id task ->
|
||||||
|
let updateTask t = if t.id == id then { t | description = task } else t
|
||||||
|
in
|
||||||
|
{ model | tasks = List.map updateTask model.tasks }
|
||||||
|
|
||||||
|
Delete id ->
|
||||||
|
{ model | tasks = List.filter (\t -> t.id /= id) model.tasks }
|
||||||
|
|
||||||
|
DeleteComplete ->
|
||||||
|
{ model | tasks = List.filter (not << .completed) model.tasks }
|
||||||
|
|
||||||
|
Check id isCompleted ->
|
||||||
|
let updateTask t = if t.id == id then { t | completed = isCompleted } else t
|
||||||
|
in
|
||||||
|
{ model | tasks = List.map updateTask model.tasks }
|
||||||
|
|
||||||
|
CheckAll isCompleted ->
|
||||||
|
let updateTask t = { t | completed = isCompleted }
|
||||||
|
in
|
||||||
|
{ model | tasks = List.map updateTask model.tasks }
|
||||||
|
|
||||||
|
ChangeVisibility visibility ->
|
||||||
|
{ model | visibility = visibility }
|
||||||
|
|
||||||
|
|
||||||
|
---- VIEW ----
|
||||||
|
|
||||||
|
view : Address Action -> Model -> Html
|
||||||
|
view address model =
|
||||||
|
div
|
||||||
|
[ class "todomvc-wrapper"
|
||||||
|
, style [ ("visibility", "hidden") ]
|
||||||
|
]
|
||||||
|
[ section
|
||||||
|
[ class "todoapp" ]
|
||||||
|
[ taskEntry address model.field
|
||||||
|
, taskList address model.visibility model.tasks
|
||||||
|
, controls address model.visibility model.tasks
|
||||||
|
]
|
||||||
|
, infoFooter
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
onEnter : Address a -> a -> Attribute
|
||||||
|
onEnter address value =
|
||||||
|
on "keydown"
|
||||||
|
(Json.customDecoder keyCode is13)
|
||||||
|
(\_ -> Signal.message address value)
|
||||||
|
|
||||||
|
|
||||||
|
is13 : Int -> Result String ()
|
||||||
|
is13 code =
|
||||||
|
if code == 13 then Ok () else Err "not the right key code"
|
||||||
|
|
||||||
|
|
||||||
|
taskEntry : Address Action -> String -> Html
|
||||||
|
taskEntry address task =
|
||||||
|
header
|
||||||
|
[ class "header" ]
|
||||||
|
[ h1 [] [ text "todos" ]
|
||||||
|
, input
|
||||||
|
[ class "new-todo"
|
||||||
|
, placeholder "What needs to be done?"
|
||||||
|
, autofocus True
|
||||||
|
, value task
|
||||||
|
, name "newTodo"
|
||||||
|
, on "input" targetValue (Signal.message address << UpdateField)
|
||||||
|
, onEnter address Add
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
taskList : Address Action -> String -> List Task -> Html
|
||||||
|
taskList address visibility tasks =
|
||||||
|
let isVisible todo =
|
||||||
|
case visibility of
|
||||||
|
"Completed" -> todo.completed
|
||||||
|
"Active" -> not todo.completed
|
||||||
|
_ -> True
|
||||||
|
|
||||||
|
allCompleted = List.all .completed tasks
|
||||||
|
|
||||||
|
cssVisibility = if List.isEmpty tasks then "hidden" else "visible"
|
||||||
|
in
|
||||||
|
section
|
||||||
|
[ class "main"
|
||||||
|
, style [ ("visibility", cssVisibility) ]
|
||||||
|
]
|
||||||
|
[ input
|
||||||
|
[ class "toggle-all"
|
||||||
|
, type' "checkbox"
|
||||||
|
, name "toggle"
|
||||||
|
, checked allCompleted
|
||||||
|
, onClick address (CheckAll (not allCompleted))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label
|
||||||
|
[ for "toggle-all" ]
|
||||||
|
[ text "Mark all as complete" ]
|
||||||
|
, ul
|
||||||
|
[ class "todo-list" ]
|
||||||
|
(List.map (todoItem address) (List.filter isVisible tasks))
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
todoItem : Address Action -> Task -> Html
|
||||||
|
todoItem address todo =
|
||||||
|
li
|
||||||
|
[ classList [ ("completed", todo.completed), ("editing", todo.editing) ] ]
|
||||||
|
[ div
|
||||||
|
[ class "view" ]
|
||||||
|
[ input
|
||||||
|
[ class "toggle"
|
||||||
|
, type' "checkbox"
|
||||||
|
, checked todo.completed
|
||||||
|
, onClick address (Check todo.id (not todo.completed))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label
|
||||||
|
[ onDoubleClick address (EditingTask todo.id True) ]
|
||||||
|
[ text todo.description ]
|
||||||
|
, button
|
||||||
|
[ class "destroy"
|
||||||
|
, onClick address (Delete todo.id)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ class "edit"
|
||||||
|
, value todo.description
|
||||||
|
, name "title"
|
||||||
|
, id ("todo-" ++ toString todo.id)
|
||||||
|
, on "input" targetValue (Signal.message address << UpdateTask todo.id)
|
||||||
|
, onBlur address (EditingTask todo.id False)
|
||||||
|
, onEnter address (EditingTask todo.id False)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
controls : Address Action -> String -> List Task -> Html
|
||||||
|
controls address visibility tasks =
|
||||||
|
let tasksCompleted = List.length (List.filter .completed tasks)
|
||||||
|
tasksLeft = List.length tasks - tasksCompleted
|
||||||
|
item_ = if tasksLeft == 1 then " item" else " items"
|
||||||
|
in
|
||||||
|
footer
|
||||||
|
[ class "footer"
|
||||||
|
, hidden (List.isEmpty tasks)
|
||||||
|
]
|
||||||
|
[ span
|
||||||
|
[ class "todo-count" ]
|
||||||
|
[ strong [] [ text (toString tasksLeft) ]
|
||||||
|
, text (item_ ++ " left")
|
||||||
|
]
|
||||||
|
, ul
|
||||||
|
[ class "filters" ]
|
||||||
|
[ visibilitySwap address "#/" "All" visibility
|
||||||
|
, text " "
|
||||||
|
, visibilitySwap address "#/active" "Active" visibility
|
||||||
|
, text " "
|
||||||
|
, visibilitySwap address "#/completed" "Completed" visibility
|
||||||
|
]
|
||||||
|
, button
|
||||||
|
[ class "clear-completed"
|
||||||
|
, hidden (tasksCompleted == 0)
|
||||||
|
, onClick address DeleteComplete
|
||||||
|
]
|
||||||
|
[ text ("Clear completed (" ++ toString tasksCompleted ++ ")") ]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
visibilitySwap : Address Action -> String -> String -> String -> Html
|
||||||
|
visibilitySwap address uri visibility actualVisibility =
|
||||||
|
li
|
||||||
|
[ onClick address (ChangeVisibility visibility) ]
|
||||||
|
[ a [ href uri, classList [("selected", visibility == actualVisibility)] ] [ text visibility ] ]
|
||||||
|
|
||||||
|
|
||||||
|
infoFooter : Html
|
||||||
|
infoFooter =
|
||||||
|
footer [ class "info" ]
|
||||||
|
[ p [] [ text "Double-click to edit a todo" ]
|
||||||
|
, p []
|
||||||
|
[ text "Written by "
|
||||||
|
, a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ]
|
||||||
|
]
|
||||||
|
, p []
|
||||||
|
[ text "Part of "
|
||||||
|
, a [ href "http://todomvc.com" ] [ text "TodoMVC" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
---- INPUTS ----
|
||||||
|
|
||||||
|
-- wire the entire application together
|
||||||
|
main : Signal Html
|
||||||
|
main =
|
||||||
|
Signal.map (view actions.address) model
|
||||||
|
|
||||||
|
|
||||||
|
-- manage the model of our application over time
|
||||||
|
model : Signal Model
|
||||||
|
model =
|
||||||
|
Signal.foldp update emptyModel actions.signal
|
||||||
|
|
||||||
|
|
||||||
|
-- actions from user input
|
||||||
|
actions : Signal.Mailbox Action
|
||||||
|
actions =
|
||||||
|
Signal.mailbox NoOp
|
||||||
|
|
||||||
|
|
||||||
|
port focus : Signal String
|
||||||
|
port focus =
|
||||||
|
let needsFocus act =
|
||||||
|
case act of
|
||||||
|
EditingTask id bool -> bool
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
toSelector act =
|
||||||
|
case act of
|
||||||
|
EditingTask id _ -> "#todo-" ++ toString id
|
||||||
|
_ -> ""
|
||||||
|
in
|
||||||
|
actions.signal
|
||||||
|
|> Signal.filter needsFocus (EditingTask 0 True)
|
||||||
|
|> Signal.map toSelector
|
||||||
15
bench/implementations/elm-0.16/elm-package.json
Normal file
15
bench/implementations/elm-0.16/elm-package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"summary": "TodoMVC created with Elm and elm-html",
|
||||||
|
"repository": "https://github.com/evancz/elm-todomvc.git",
|
||||||
|
"license": "BSD3",
|
||||||
|
"source-directories": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"exposed-modules": [],
|
||||||
|
"dependencies": {
|
||||||
|
"elm-lang/core": "3.0.0 <= v < 4.0.0",
|
||||||
|
"evancz/elm-html": "4.0.2 <= v < 5.0.0"
|
||||||
|
},
|
||||||
|
"elm-version": "0.16.0 <= v < 0.17.0"
|
||||||
|
}
|
||||||
10534
bench/implementations/elm-0.16/elm.js
Normal file
10534
bench/implementations/elm-0.16/elm.js
Normal file
File diff suppressed because it is too large
Load diff
26
bench/implementations/elm-0.16/index.html
Normal file
26
bench/implementations/elm-0.16/index.html
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Elm • TodoMVC</title>
|
||||||
|
<script type="text/javascript" src="elm.js"></script>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var todomvc = Elm.fullscreen(Elm.Todo);
|
||||||
|
todomvc.ports.focus.subscribe(function(selector) {
|
||||||
|
setTimeout(function() {
|
||||||
|
var nodes = document.querySelectorAll(selector);
|
||||||
|
if (nodes.length === 1 && document.activeElement !== nodes[0]) {
|
||||||
|
nodes[0].focus();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
382
bench/implementations/elm-0.16/style.css
Normal file
382
bench/implementations/elm-0.16/style.css
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todomvc-wrapper {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
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; /* Mobile Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto 0;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
-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-line;
|
||||||
|
word-break: break-all;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hack to remove background from Mobile Safari.
|
||||||
|
Can't use it globally since it destroys checkboxes in Firefox
|
||||||
|
*/
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
381
bench/implementations/elm-0.17-optimized/Todo.elm
Executable file
381
bench/implementations/elm-0.17-optimized/Todo.elm
Executable file
|
|
@ -0,0 +1,381 @@
|
||||||
|
port module Todo exposing (main)
|
||||||
|
{-| TodoMVC implemented in Elm, using plain HTML and CSS for rendering.
|
||||||
|
|
||||||
|
This application is broken up into three key parts:
|
||||||
|
|
||||||
|
1. Model - a full definition of the application's state
|
||||||
|
2. Update - a way to step the application state forward
|
||||||
|
3. View - a way to visualize our application state with HTML
|
||||||
|
|
||||||
|
This clean division of concerns is a core part of Elm. You can read more about
|
||||||
|
this in <http://guide.elm-lang.org/architecture/index.html>
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.App as App
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Html.Events exposing (..)
|
||||||
|
import Html.Keyed as Keyed
|
||||||
|
import Html.Lazy exposing (lazy, lazy2)
|
||||||
|
import Json.Decode as Json
|
||||||
|
import String
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
main : Program Never
|
||||||
|
main =
|
||||||
|
App.program
|
||||||
|
{ init = init
|
||||||
|
, view = view
|
||||||
|
, update = update
|
||||||
|
, subscriptions = \_ -> Sub.none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
port focus : String -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- MODEL
|
||||||
|
|
||||||
|
|
||||||
|
-- The full application state of our todo app.
|
||||||
|
type alias Model =
|
||||||
|
{ entries : List Entry
|
||||||
|
, field : String
|
||||||
|
, uid : Int
|
||||||
|
, visibility : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Entry =
|
||||||
|
{ description : String
|
||||||
|
, completed : Bool
|
||||||
|
, editing : Bool
|
||||||
|
, id : Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
emptyModel : Model
|
||||||
|
emptyModel =
|
||||||
|
{ entries = []
|
||||||
|
, visibility = "All"
|
||||||
|
, field = ""
|
||||||
|
, uid = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
newEntry : String -> Int -> Entry
|
||||||
|
newEntry desc id =
|
||||||
|
{ description = desc
|
||||||
|
, completed = False
|
||||||
|
, editing = False
|
||||||
|
, id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init : ( Model, Cmd Msg )
|
||||||
|
init =
|
||||||
|
emptyModel ! []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
{-| Users of our app can trigger messages by clicking and typing. These
|
||||||
|
messages are fed into the `update` function as they occur, letting us react
|
||||||
|
to them.
|
||||||
|
-}
|
||||||
|
type Msg
|
||||||
|
= NoOp
|
||||||
|
| UpdateField String
|
||||||
|
| EditingEntry Int Bool
|
||||||
|
| UpdateEntry Int String
|
||||||
|
| Add
|
||||||
|
| Delete Int
|
||||||
|
| DeleteComplete
|
||||||
|
| Check Int Bool
|
||||||
|
| CheckAll Bool
|
||||||
|
| ChangeVisibility String
|
||||||
|
|
||||||
|
|
||||||
|
-- How we update our Model on a given Msg?
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
NoOp ->
|
||||||
|
model ! []
|
||||||
|
|
||||||
|
Add ->
|
||||||
|
{ model
|
||||||
|
| uid = model.uid + 1
|
||||||
|
, field = ""
|
||||||
|
, entries =
|
||||||
|
if String.isEmpty model.field then
|
||||||
|
model.entries
|
||||||
|
else
|
||||||
|
model.entries ++ [newEntry model.field model.uid]
|
||||||
|
}
|
||||||
|
! []
|
||||||
|
|
||||||
|
UpdateField str ->
|
||||||
|
{ model | field = str }
|
||||||
|
! []
|
||||||
|
|
||||||
|
EditingEntry id isEditing ->
|
||||||
|
let
|
||||||
|
updateEntry t =
|
||||||
|
if t.id == id then { t | editing = isEditing } else t
|
||||||
|
in
|
||||||
|
{ model | entries = List.map updateEntry model.entries }
|
||||||
|
! [ focus ("#todo-" ++ toString id) ]
|
||||||
|
|
||||||
|
UpdateEntry id task ->
|
||||||
|
let
|
||||||
|
updateEntry t =
|
||||||
|
if t.id == id then { t | description = task } else t
|
||||||
|
in
|
||||||
|
{ model | entries = List.map updateEntry model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
Delete id ->
|
||||||
|
{ model | entries = List.filter (\t -> t.id /= id) model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
DeleteComplete ->
|
||||||
|
{ model | entries = List.filter (not << .completed) model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
Check id isCompleted ->
|
||||||
|
let
|
||||||
|
updateEntry t =
|
||||||
|
if t.id == id then { t | completed = isCompleted } else t
|
||||||
|
in
|
||||||
|
{ model | entries = List.map updateEntry model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
CheckAll isCompleted ->
|
||||||
|
let
|
||||||
|
updateEntry t =
|
||||||
|
{ t | completed = isCompleted }
|
||||||
|
in
|
||||||
|
{ model | entries = List.map updateEntry model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
ChangeVisibility visibility ->
|
||||||
|
{ model | visibility = visibility }
|
||||||
|
! []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> Html Msg
|
||||||
|
view model =
|
||||||
|
div
|
||||||
|
[ class "todomvc-wrapper"
|
||||||
|
, style [ ("visibility", "hidden") ]
|
||||||
|
]
|
||||||
|
[ section
|
||||||
|
[ class "todoapp" ]
|
||||||
|
[ lazy viewInput model.field
|
||||||
|
, lazy2 viewEntries model.visibility model.entries
|
||||||
|
, lazy2 viewControls model.visibility model.entries
|
||||||
|
]
|
||||||
|
, infoFooter
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewInput : String -> Html Msg
|
||||||
|
viewInput task =
|
||||||
|
header
|
||||||
|
[ class "header" ]
|
||||||
|
[ h1 [] [ text "todos" ]
|
||||||
|
, input
|
||||||
|
[ class "new-todo"
|
||||||
|
, placeholder "What needs to be done?"
|
||||||
|
, autofocus True
|
||||||
|
, value task
|
||||||
|
, name "newTodo"
|
||||||
|
, onInput UpdateField
|
||||||
|
, onEnter Add
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
onEnter : Msg -> Attribute Msg
|
||||||
|
onEnter msg =
|
||||||
|
let
|
||||||
|
tagger code =
|
||||||
|
if code == 13 then msg else NoOp
|
||||||
|
in
|
||||||
|
on "keydown" (Json.map tagger keyCode)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW ALL ENTRIES
|
||||||
|
|
||||||
|
|
||||||
|
viewEntries : String -> List Entry -> Html Msg
|
||||||
|
viewEntries visibility entries =
|
||||||
|
let
|
||||||
|
isVisible todo =
|
||||||
|
case visibility of
|
||||||
|
"Completed" -> todo.completed
|
||||||
|
"Active" -> not todo.completed
|
||||||
|
_ -> True
|
||||||
|
|
||||||
|
allCompleted =
|
||||||
|
List.all .completed entries
|
||||||
|
|
||||||
|
cssVisibility =
|
||||||
|
if List.isEmpty entries then "hidden" else "visible"
|
||||||
|
in
|
||||||
|
section
|
||||||
|
[ class "main"
|
||||||
|
, style [ ("visibility", cssVisibility) ]
|
||||||
|
]
|
||||||
|
[ input
|
||||||
|
[ class "toggle-all"
|
||||||
|
, type' "checkbox"
|
||||||
|
, name "toggle"
|
||||||
|
, checked allCompleted
|
||||||
|
, onClick (CheckAll (not allCompleted))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label
|
||||||
|
[ for "toggle-all" ]
|
||||||
|
[ text "Mark all as complete" ]
|
||||||
|
, Keyed.ul [ class "todo-list" ] <|
|
||||||
|
List.map viewKeyedEntry (List.filter isVisible entries)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW INDIVIDUAL ENTRIES
|
||||||
|
|
||||||
|
|
||||||
|
viewKeyedEntry : Entry -> (String, Html Msg)
|
||||||
|
viewKeyedEntry todo =
|
||||||
|
( toString todo.id, lazy viewEntry todo )
|
||||||
|
|
||||||
|
|
||||||
|
viewEntry : Entry -> Html Msg
|
||||||
|
viewEntry todo =
|
||||||
|
li
|
||||||
|
[ classList [ ("completed", todo.completed), ("editing", todo.editing) ] ]
|
||||||
|
[ div
|
||||||
|
[ class "view" ]
|
||||||
|
[ input
|
||||||
|
[ class "toggle"
|
||||||
|
, type' "checkbox"
|
||||||
|
, checked todo.completed
|
||||||
|
, onClick (Check todo.id (not todo.completed))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label
|
||||||
|
[ onDoubleClick (EditingEntry todo.id True) ]
|
||||||
|
[ text todo.description ]
|
||||||
|
, button
|
||||||
|
[ class "destroy"
|
||||||
|
, onClick (Delete todo.id)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ class "edit"
|
||||||
|
, value todo.description
|
||||||
|
, name "title"
|
||||||
|
, id ("todo-" ++ toString todo.id)
|
||||||
|
, onInput (UpdateEntry todo.id)
|
||||||
|
, onBlur (EditingEntry todo.id False)
|
||||||
|
, onEnter (EditingEntry todo.id False)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW CONTROLS AND FOOTER
|
||||||
|
|
||||||
|
|
||||||
|
viewControls : String -> List Entry -> Html Msg
|
||||||
|
viewControls visibility entries =
|
||||||
|
let
|
||||||
|
entriesCompleted =
|
||||||
|
List.length (List.filter .completed entries)
|
||||||
|
|
||||||
|
entriesLeft =
|
||||||
|
List.length entries - entriesCompleted
|
||||||
|
in
|
||||||
|
footer
|
||||||
|
[ class "footer"
|
||||||
|
, hidden (List.isEmpty entries)
|
||||||
|
]
|
||||||
|
[ lazy viewControlsCount entriesLeft
|
||||||
|
, lazy viewControlsFilters visibility
|
||||||
|
, lazy viewControlsClear entriesCompleted
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewControlsCount : Int -> Html Msg
|
||||||
|
viewControlsCount entriesLeft =
|
||||||
|
let
|
||||||
|
item_ =
|
||||||
|
if entriesLeft == 1 then " item" else " items"
|
||||||
|
in
|
||||||
|
span
|
||||||
|
[ class "todo-count" ]
|
||||||
|
[ strong [] [ text (toString entriesLeft) ]
|
||||||
|
, text (item_ ++ " left")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewControlsFilters : String -> Html Msg
|
||||||
|
viewControlsFilters visibility =
|
||||||
|
ul
|
||||||
|
[ class "filters" ]
|
||||||
|
[ visibilitySwap "#/" "All" visibility
|
||||||
|
, text " "
|
||||||
|
, visibilitySwap "#/active" "Active" visibility
|
||||||
|
, text " "
|
||||||
|
, visibilitySwap "#/completed" "Completed" visibility
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
visibilitySwap : String -> String -> String -> Html Msg
|
||||||
|
visibilitySwap uri visibility actualVisibility =
|
||||||
|
li
|
||||||
|
[ onClick (ChangeVisibility visibility) ]
|
||||||
|
[ a [ href uri, classList [("selected", visibility == actualVisibility)] ]
|
||||||
|
[ text visibility ]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewControlsClear : Int -> Html Msg
|
||||||
|
viewControlsClear entriesCompleted =
|
||||||
|
button
|
||||||
|
[ class "clear-completed"
|
||||||
|
, hidden (entriesCompleted == 0)
|
||||||
|
, onClick DeleteComplete
|
||||||
|
]
|
||||||
|
[ text ("Clear completed (" ++ toString entriesCompleted ++ ")")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
infoFooter : Html msg
|
||||||
|
infoFooter =
|
||||||
|
footer [ class "info" ]
|
||||||
|
[ p [] [ text "Double-click to edit a todo" ]
|
||||||
|
, p []
|
||||||
|
[ text "Written by "
|
||||||
|
, a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ]
|
||||||
|
]
|
||||||
|
, p []
|
||||||
|
[ text "Part of "
|
||||||
|
, a [ href "http://todomvc.com" ] [ text "TodoMVC" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
15
bench/implementations/elm-0.17-optimized/elm-package.json
Normal file
15
bench/implementations/elm-0.17-optimized/elm-package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"summary": "TodoMVC created with Elm and elm-html",
|
||||||
|
"repository": "https://github.com/evancz/elm-todomvc.git",
|
||||||
|
"license": "BSD3",
|
||||||
|
"source-directories": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"exposed-modules": [],
|
||||||
|
"dependencies": {
|
||||||
|
"elm-lang/core": "4.0.0 <= v < 5.0.0",
|
||||||
|
"elm-lang/html": "1.0.0 <= v < 2.0.0"
|
||||||
|
},
|
||||||
|
"elm-version": "0.17.0 <= v < 0.18.0"
|
||||||
|
}
|
||||||
8282
bench/implementations/elm-0.17-optimized/elm.js
Executable file
8282
bench/implementations/elm-0.17-optimized/elm.js
Executable file
File diff suppressed because it is too large
Load diff
29
bench/implementations/elm-0.17-optimized/index.html
Normal file
29
bench/implementations/elm-0.17-optimized/index.html
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Elm • TodoMVC</title>
|
||||||
|
<script type="text/javascript" src="elm.js"></script>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var todomvc = Elm.Todo.fullscreen();
|
||||||
|
todomvc.ports.focus.subscribe(function(selector)
|
||||||
|
{
|
||||||
|
setTimeout(function()
|
||||||
|
{
|
||||||
|
var nodes = document.querySelectorAll(selector);
|
||||||
|
if (nodes.length === 1 && document.activeElement !== nodes[0])
|
||||||
|
{
|
||||||
|
nodes[0].focus();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
382
bench/implementations/elm-0.17-optimized/style.css
Normal file
382
bench/implementations/elm-0.17-optimized/style.css
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todomvc-wrapper {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
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; /* Mobile Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto 0;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
-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-line;
|
||||||
|
word-break: break-all;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hack to remove background from Mobile Safari.
|
||||||
|
Can't use it globally since it destroys checkboxes in Firefox
|
||||||
|
*/
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
374
bench/implementations/elm-0.17/Todo.elm
Normal file
374
bench/implementations/elm-0.17/Todo.elm
Normal file
|
|
@ -0,0 +1,374 @@
|
||||||
|
port module Todo exposing (main)
|
||||||
|
{-| TodoMVC implemented in Elm, using plain HTML and CSS for rendering.
|
||||||
|
|
||||||
|
This application is broken up into three key parts:
|
||||||
|
|
||||||
|
1. Model - a full definition of the application's state
|
||||||
|
2. Update - a way to step the application state forward
|
||||||
|
3. View - a way to visualize our application state with HTML
|
||||||
|
|
||||||
|
This clean division of concerns is a core part of Elm. You can read more about
|
||||||
|
this in <http://guide.elm-lang.org/architecture/index.html>
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.App as App
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Html.Events exposing (..)
|
||||||
|
import Json.Decode as Json
|
||||||
|
import String
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
main : Program Never
|
||||||
|
main =
|
||||||
|
App.program
|
||||||
|
{ init = init
|
||||||
|
, view = view
|
||||||
|
, update = update
|
||||||
|
, subscriptions = \_ -> Sub.none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
port focus : String -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- MODEL
|
||||||
|
|
||||||
|
|
||||||
|
-- The full application state of our todo app.
|
||||||
|
type alias Model =
|
||||||
|
{ entries : List Entry
|
||||||
|
, field : String
|
||||||
|
, uid : Int
|
||||||
|
, visibility : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Entry =
|
||||||
|
{ description : String
|
||||||
|
, completed : Bool
|
||||||
|
, editing : Bool
|
||||||
|
, id : Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
emptyModel : Model
|
||||||
|
emptyModel =
|
||||||
|
{ entries = []
|
||||||
|
, visibility = "All"
|
||||||
|
, field = ""
|
||||||
|
, uid = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
newEntry : String -> Int -> Entry
|
||||||
|
newEntry desc id =
|
||||||
|
{ description = desc
|
||||||
|
, completed = False
|
||||||
|
, editing = False
|
||||||
|
, id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init : ( Model, Cmd Msg )
|
||||||
|
init =
|
||||||
|
emptyModel ! []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
{-| Users of our app can trigger messages by clicking and typing. These
|
||||||
|
messages are fed into the `update` function as they occur, letting us react
|
||||||
|
to them.
|
||||||
|
-}
|
||||||
|
type Msg
|
||||||
|
= NoOp
|
||||||
|
| UpdateField String
|
||||||
|
| EditingEntry Int Bool
|
||||||
|
| UpdateEntry Int String
|
||||||
|
| Add
|
||||||
|
| Delete Int
|
||||||
|
| DeleteComplete
|
||||||
|
| Check Int Bool
|
||||||
|
| CheckAll Bool
|
||||||
|
| ChangeVisibility String
|
||||||
|
|
||||||
|
|
||||||
|
-- How we update our Model on a given Msg?
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
NoOp ->
|
||||||
|
model ! []
|
||||||
|
|
||||||
|
Add ->
|
||||||
|
{ model
|
||||||
|
| uid = model.uid + 1
|
||||||
|
, field = ""
|
||||||
|
, entries =
|
||||||
|
if String.isEmpty model.field then
|
||||||
|
model.entries
|
||||||
|
else
|
||||||
|
model.entries ++ [newEntry model.field model.uid]
|
||||||
|
}
|
||||||
|
! []
|
||||||
|
|
||||||
|
UpdateField str ->
|
||||||
|
{ model | field = str }
|
||||||
|
! []
|
||||||
|
|
||||||
|
EditingEntry id isEditing ->
|
||||||
|
let
|
||||||
|
updateEntry t =
|
||||||
|
if t.id == id then { t | editing = isEditing } else t
|
||||||
|
in
|
||||||
|
{ model | entries = List.map updateEntry model.entries }
|
||||||
|
! [ focus ("#todo-" ++ toString id) ]
|
||||||
|
|
||||||
|
UpdateEntry id task ->
|
||||||
|
let
|
||||||
|
updateEntry t =
|
||||||
|
if t.id == id then { t | description = task } else t
|
||||||
|
in
|
||||||
|
{ model | entries = List.map updateEntry model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
Delete id ->
|
||||||
|
{ model | entries = List.filter (\t -> t.id /= id) model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
DeleteComplete ->
|
||||||
|
{ model | entries = List.filter (not << .completed) model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
Check id isCompleted ->
|
||||||
|
let
|
||||||
|
updateEntry t =
|
||||||
|
if t.id == id then { t | completed = isCompleted } else t
|
||||||
|
in
|
||||||
|
{ model | entries = List.map updateEntry model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
CheckAll isCompleted ->
|
||||||
|
let
|
||||||
|
updateEntry t =
|
||||||
|
{ t | completed = isCompleted }
|
||||||
|
in
|
||||||
|
{ model | entries = List.map updateEntry model.entries }
|
||||||
|
! []
|
||||||
|
|
||||||
|
ChangeVisibility visibility ->
|
||||||
|
{ model | visibility = visibility }
|
||||||
|
! []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> Html Msg
|
||||||
|
view model =
|
||||||
|
div
|
||||||
|
[ class "todomvc-wrapper"
|
||||||
|
, style [ ("visibility", "hidden") ]
|
||||||
|
]
|
||||||
|
[ section
|
||||||
|
[ class "todoapp" ]
|
||||||
|
[ viewInput model.field
|
||||||
|
, viewEntries model.visibility model.entries
|
||||||
|
, viewControls model.visibility model.entries
|
||||||
|
]
|
||||||
|
, infoFooter
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewInput : String -> Html Msg
|
||||||
|
viewInput task =
|
||||||
|
header
|
||||||
|
[ class "header" ]
|
||||||
|
[ h1 [] [ text "todos" ]
|
||||||
|
, input
|
||||||
|
[ class "new-todo"
|
||||||
|
, placeholder "What needs to be done?"
|
||||||
|
, autofocus True
|
||||||
|
, value task
|
||||||
|
, name "newTodo"
|
||||||
|
, onInput UpdateField
|
||||||
|
, onEnter Add
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
onEnter : Msg -> Attribute Msg
|
||||||
|
onEnter msg =
|
||||||
|
let
|
||||||
|
tagger code =
|
||||||
|
if code == 13 then msg else NoOp
|
||||||
|
in
|
||||||
|
on "keydown" (Json.map tagger keyCode)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW ALL ENTRIES
|
||||||
|
|
||||||
|
|
||||||
|
viewEntries : String -> List Entry -> Html Msg
|
||||||
|
viewEntries visibility entries =
|
||||||
|
let
|
||||||
|
isVisible todo =
|
||||||
|
case visibility of
|
||||||
|
"Completed" -> todo.completed
|
||||||
|
"Active" -> not todo.completed
|
||||||
|
_ -> True
|
||||||
|
|
||||||
|
allCompleted =
|
||||||
|
List.all .completed entries
|
||||||
|
|
||||||
|
cssVisibility =
|
||||||
|
if List.isEmpty entries then "hidden" else "visible"
|
||||||
|
in
|
||||||
|
section
|
||||||
|
[ class "main"
|
||||||
|
, style [ ("visibility", cssVisibility) ]
|
||||||
|
]
|
||||||
|
[ input
|
||||||
|
[ class "toggle-all"
|
||||||
|
, type' "checkbox"
|
||||||
|
, name "toggle"
|
||||||
|
, checked allCompleted
|
||||||
|
, onClick (CheckAll (not allCompleted))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label
|
||||||
|
[ for "toggle-all" ]
|
||||||
|
[ text "Mark all as complete" ]
|
||||||
|
, ul [ class "todo-list" ] <|
|
||||||
|
List.map viewEntry (List.filter isVisible entries)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW INDIVIDUAL ENTRIES
|
||||||
|
|
||||||
|
|
||||||
|
viewEntry : Entry -> Html Msg
|
||||||
|
viewEntry todo =
|
||||||
|
li
|
||||||
|
[ classList [ ("completed", todo.completed), ("editing", todo.editing) ] ]
|
||||||
|
[ div
|
||||||
|
[ class "view" ]
|
||||||
|
[ input
|
||||||
|
[ class "toggle"
|
||||||
|
, type' "checkbox"
|
||||||
|
, checked todo.completed
|
||||||
|
, onClick (Check todo.id (not todo.completed))
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, label
|
||||||
|
[ onDoubleClick (EditingEntry todo.id True) ]
|
||||||
|
[ text todo.description ]
|
||||||
|
, button
|
||||||
|
[ class "destroy"
|
||||||
|
, onClick (Delete todo.id)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ class "edit"
|
||||||
|
, value todo.description
|
||||||
|
, name "title"
|
||||||
|
, id ("todo-" ++ toString todo.id)
|
||||||
|
, onInput (UpdateEntry todo.id)
|
||||||
|
, onBlur (EditingEntry todo.id False)
|
||||||
|
, onEnter (EditingEntry todo.id False)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW CONTROLS AND FOOTER
|
||||||
|
|
||||||
|
|
||||||
|
viewControls : String -> List Entry -> Html Msg
|
||||||
|
viewControls visibility entries =
|
||||||
|
let
|
||||||
|
entriesCompleted =
|
||||||
|
List.length (List.filter .completed entries)
|
||||||
|
|
||||||
|
entriesLeft =
|
||||||
|
List.length entries - entriesCompleted
|
||||||
|
in
|
||||||
|
footer
|
||||||
|
[ class "footer"
|
||||||
|
, hidden (List.isEmpty entries)
|
||||||
|
]
|
||||||
|
[ viewControlsCount entriesLeft
|
||||||
|
, viewControlsFilters visibility
|
||||||
|
, viewControlsClear entriesCompleted
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewControlsCount : Int -> Html Msg
|
||||||
|
viewControlsCount entriesLeft =
|
||||||
|
let
|
||||||
|
item_ =
|
||||||
|
if entriesLeft == 1 then " item" else " items"
|
||||||
|
in
|
||||||
|
span
|
||||||
|
[ class "todo-count" ]
|
||||||
|
[ strong [] [ text (toString entriesLeft) ]
|
||||||
|
, text (item_ ++ " left")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewControlsFilters : String -> Html Msg
|
||||||
|
viewControlsFilters visibility =
|
||||||
|
ul
|
||||||
|
[ class "filters" ]
|
||||||
|
[ visibilitySwap "#/" "All" visibility
|
||||||
|
, text " "
|
||||||
|
, visibilitySwap "#/active" "Active" visibility
|
||||||
|
, text " "
|
||||||
|
, visibilitySwap "#/completed" "Completed" visibility
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
visibilitySwap : String -> String -> String -> Html Msg
|
||||||
|
visibilitySwap uri visibility actualVisibility =
|
||||||
|
li
|
||||||
|
[ onClick (ChangeVisibility visibility) ]
|
||||||
|
[ a [ href uri, classList [("selected", visibility == actualVisibility)] ]
|
||||||
|
[ text visibility ]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewControlsClear : Int -> Html Msg
|
||||||
|
viewControlsClear entriesCompleted =
|
||||||
|
button
|
||||||
|
[ class "clear-completed"
|
||||||
|
, hidden (entriesCompleted == 0)
|
||||||
|
, onClick DeleteComplete
|
||||||
|
]
|
||||||
|
[ text ("Clear completed (" ++ toString entriesCompleted ++ ")")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
infoFooter : Html msg
|
||||||
|
infoFooter =
|
||||||
|
footer [ class "info" ]
|
||||||
|
[ p [] [ text "Double-click to edit a todo" ]
|
||||||
|
, p []
|
||||||
|
[ text "Written by "
|
||||||
|
, a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ]
|
||||||
|
]
|
||||||
|
, p []
|
||||||
|
[ text "Part of "
|
||||||
|
, a [ href "http://todomvc.com" ] [ text "TodoMVC" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
15
bench/implementations/elm-0.17/elm-package.json
Normal file
15
bench/implementations/elm-0.17/elm-package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"summary": "TodoMVC created with Elm and elm-html",
|
||||||
|
"repository": "https://github.com/evancz/elm-todomvc.git",
|
||||||
|
"license": "BSD3",
|
||||||
|
"source-directories": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"exposed-modules": [],
|
||||||
|
"dependencies": {
|
||||||
|
"elm-lang/core": "4.0.0 <= v < 5.0.0",
|
||||||
|
"elm-lang/html": "1.0.0 <= v < 2.0.0"
|
||||||
|
},
|
||||||
|
"elm-version": "0.17.0 <= v < 0.18.0"
|
||||||
|
}
|
||||||
8512
bench/implementations/elm-0.17/elm.js
Normal file
8512
bench/implementations/elm-0.17/elm.js
Normal file
File diff suppressed because it is too large
Load diff
29
bench/implementations/elm-0.17/index.html
Normal file
29
bench/implementations/elm-0.17/index.html
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Elm • TodoMVC</title>
|
||||||
|
<script type="text/javascript" src="elm.js"></script>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var todomvc = Elm.Todo.fullscreen();
|
||||||
|
todomvc.ports.focus.subscribe(function(selector)
|
||||||
|
{
|
||||||
|
setTimeout(function()
|
||||||
|
{
|
||||||
|
var nodes = document.querySelectorAll(selector);
|
||||||
|
if (nodes.length === 1 && document.activeElement !== nodes[0])
|
||||||
|
{
|
||||||
|
nodes[0].focus();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
||||||
382
bench/implementations/elm-0.17/style.css
Normal file
382
bench/implementations/elm-0.17/style.css
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todomvc-wrapper {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
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; /* Mobile Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto 0;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
-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-line;
|
||||||
|
word-break: break-all;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hack to remove background from Mobile Safari.
|
||||||
|
Can't use it globally since it destroys checkboxes in Firefox
|
||||||
|
*/
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
bench/implementations/ember-2.6.3/.gitignore
vendored
Normal file
16
bench/implementations/ember-2.6.3/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# compiled output
|
||||||
|
/tmp
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/bower_components
|
||||||
|
|
||||||
|
# misc
|
||||||
|
/.sass-cache
|
||||||
|
/connect.lock
|
||||||
|
/coverage/*
|
||||||
|
/libpeerconnection.log
|
||||||
|
npm-debug.log
|
||||||
|
testem.log
|
||||||
15
bench/implementations/ember-2.6.3/README.md
Executable file
15
bench/implementations/ember-2.6.3/README.md
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Ember.js TodoMVC for Benchmarking
|
||||||
|
|
||||||
|
This is an update of the
|
||||||
|
_[Ember.js TodoMVC example](https://github.com/tastejs/todomvc/tree/gh-pages/examples/emberjs)_
|
||||||
|
with the following changes:
|
||||||
|
|
||||||
|
* Upgrade to Ember 2.6.2
|
||||||
|
* Configure router to support relative URLs
|
||||||
|
* Remove localStorage functionality - not relevant to measuring render performance
|
||||||
|
|
||||||
|
# Building
|
||||||
|
|
||||||
|
1. `npm install`
|
||||||
|
2. `npm run make`
|
||||||
|
3. `open dist/index.html`
|
||||||
18
bench/implementations/ember-2.6.3/app/app.js
Executable file
18
bench/implementations/ember-2.6.3/app/app.js
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import Resolver from './resolver';
|
||||||
|
import loadInitializers from 'ember-load-initializers';
|
||||||
|
import config from './config/environment';
|
||||||
|
|
||||||
|
let App;
|
||||||
|
|
||||||
|
Ember.MODEL_FACTORY_INJECTIONS = true;
|
||||||
|
|
||||||
|
App = Ember.Application.extend({
|
||||||
|
modulePrefix: config.modulePrefix,
|
||||||
|
podModulePrefix: config.podModulePrefix,
|
||||||
|
Resolver
|
||||||
|
});
|
||||||
|
|
||||||
|
loadInitializers(App, config.modulePrefix);
|
||||||
|
|
||||||
|
export default App;
|
||||||
48
bench/implementations/ember-2.6.3/app/components/todo-item.js
Executable file
48
bench/implementations/ember-2.6.3/app/components/todo-item.js
Executable file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
repo: Ember.inject.service(),
|
||||||
|
tagName: 'li',
|
||||||
|
editing: false,
|
||||||
|
classNameBindings: ['todo.completed', 'editing'],
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
startEditing() {
|
||||||
|
this.get('onStartEdit')();
|
||||||
|
this.set('editing', true);
|
||||||
|
Ember.run.scheduleOnce('afterRender', this, 'focusInput');
|
||||||
|
},
|
||||||
|
|
||||||
|
doneEditing(todoTitle) {
|
||||||
|
if (!this.get('editing')) { return; }
|
||||||
|
if (Ember.isBlank(todoTitle)) {
|
||||||
|
this.send('removeTodo');
|
||||||
|
} else {
|
||||||
|
this.set('todo.title', todoTitle.trim());
|
||||||
|
this.set('editing', false);
|
||||||
|
this.get('onEndEdit')();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleKeydown(e) {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
e.target.blur();
|
||||||
|
} else if (e.keyCode === 27) {
|
||||||
|
this.set('editing', false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleCompleted(e) {
|
||||||
|
let todo = this.get('todo');
|
||||||
|
Ember.set(todo, 'completed', e.target.checked);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeTodo() {
|
||||||
|
this.get('repo').delete(this.get('todo'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
focusInput() {
|
||||||
|
this.element.querySelector('input.edit').focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
26
bench/implementations/ember-2.6.3/app/components/todo-list.js
Executable file
26
bench/implementations/ember-2.6.3/app/components/todo-list.js
Executable file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
repo: Ember.inject.service(),
|
||||||
|
tagName: 'section',
|
||||||
|
elementId: 'main',
|
||||||
|
canToggle: true,
|
||||||
|
allCompleted: Ember.computed('todos.@each.completed', function () {
|
||||||
|
return this.get('todos').isEvery('completed');
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
enableToggle() {
|
||||||
|
this.set('canToggle', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
disableToggle() {
|
||||||
|
this.set('canToggle', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleAll() {
|
||||||
|
let allCompleted = this.get('allCompleted');
|
||||||
|
this.get('todos').forEach(todo => Ember.set(todo, 'completed', !allCompleted));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
5
bench/implementations/ember-2.6.3/app/controllers/active.js
Executable file
5
bench/implementations/ember-2.6.3/app/controllers/active.js
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Controller.extend({
|
||||||
|
todos: Ember.computed.filterBy('model', 'completed', false)
|
||||||
|
});
|
||||||
19
bench/implementations/ember-2.6.3/app/controllers/application.js
Executable file
19
bench/implementations/ember-2.6.3/app/controllers/application.js
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Controller.extend({
|
||||||
|
repo: Ember.inject.service(),
|
||||||
|
remaining: Ember.computed.filterBy('model', 'completed', false),
|
||||||
|
completed: Ember.computed.filterBy('model', 'completed'),
|
||||||
|
actions: {
|
||||||
|
createTodo(e) {
|
||||||
|
if (e.keyCode === 13 && !Ember.isBlank(e.target.value)) {
|
||||||
|
this.get('repo').add({ title: e.target.value.trim(), completed: false });
|
||||||
|
e.target.value = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearCompleted() {
|
||||||
|
this.get('model').removeObjects(this.get('completed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
5
bench/implementations/ember-2.6.3/app/controllers/completed.js
Executable file
5
bench/implementations/ember-2.6.3/app/controllers/completed.js
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Controller.extend({
|
||||||
|
todos: Ember.computed.filterBy('model', 'completed', true)
|
||||||
|
});
|
||||||
7
bench/implementations/ember-2.6.3/app/helpers/gt.js
Executable file
7
bench/implementations/ember-2.6.3/app/helpers/gt.js
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export function gt([n1, n2]/*, hash*/) {
|
||||||
|
return n1 > n2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ember.Helper.helper(gt);
|
||||||
8
bench/implementations/ember-2.6.3/app/helpers/pluralize.js
Executable file
8
bench/implementations/ember-2.6.3/app/helpers/pluralize.js
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import { pluralize } from 'ember-inflector';
|
||||||
|
|
||||||
|
export function pluralizeHelper([singular, count]/*, hash*/) {
|
||||||
|
return count === 1 ? singular : pluralize(singular);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ember.Helper.helper(pluralizeHelper);
|
||||||
24
bench/implementations/ember-2.6.3/app/index.html
Executable file
24
bench/implementations/ember-2.6.3/app/index.html
Executable file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<title>Todomvc</title>
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
{{content-for "head"}}
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="assets/vendor.css">
|
||||||
|
<link rel="stylesheet" href="assets/todomvc.css">
|
||||||
|
|
||||||
|
{{content-for "head-footer"}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{content-for "body"}}
|
||||||
|
|
||||||
|
<script src="assets/vendor.js"></script>
|
||||||
|
<script src="assets/todomvc.js"></script>
|
||||||
|
|
||||||
|
{{content-for "body-footer"}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
bench/implementations/ember-2.6.3/app/resolver.js
Executable file
3
bench/implementations/ember-2.6.3/app/resolver.js
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Resolver from 'ember-resolver';
|
||||||
|
|
||||||
|
export default Resolver;
|
||||||
13
bench/implementations/ember-2.6.3/app/router.js
Executable file
13
bench/implementations/ember-2.6.3/app/router.js
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
import config from './config/environment';
|
||||||
|
|
||||||
|
const Router = Ember.Router.extend({
|
||||||
|
location: config.locationType
|
||||||
|
});
|
||||||
|
|
||||||
|
Router.map(function () {
|
||||||
|
this.route('active');
|
||||||
|
this.route('completed');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Router;
|
||||||
8
bench/implementations/ember-2.6.3/app/routes/application.js
Executable file
8
bench/implementations/ember-2.6.3/app/routes/application.js
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Route.extend({
|
||||||
|
repo: Ember.inject.service(),
|
||||||
|
model() {
|
||||||
|
return this.get('repo').findAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
19
bench/implementations/ember-2.6.3/app/services/repo.js
Executable file
19
bench/implementations/ember-2.6.3/app/services/repo.js
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Service.extend({
|
||||||
|
lastId: 0,
|
||||||
|
data: null,
|
||||||
|
findAll() {
|
||||||
|
return this.get('data') || this.set('data', []);
|
||||||
|
},
|
||||||
|
|
||||||
|
add(attrs) {
|
||||||
|
let todo = Object.assign({ id: this.incrementProperty('lastId') }, attrs);
|
||||||
|
this.get('data').pushObject(todo);
|
||||||
|
return todo;
|
||||||
|
},
|
||||||
|
|
||||||
|
delete(todo) {
|
||||||
|
this.get('data').removeObject(todo);
|
||||||
|
}
|
||||||
|
});
|
||||||
0
bench/implementations/ember-2.6.3/app/styles/app.css
Executable file
0
bench/implementations/ember-2.6.3/app/styles/app.css
Executable file
1
bench/implementations/ember-2.6.3/app/templates/active.hbs
Executable file
1
bench/implementations/ember-2.6.3/app/templates/active.hbs
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
{{todo-list todos=todos}}
|
||||||
29
bench/implementations/ember-2.6.3/app/templates/application.hbs
Executable file
29
bench/implementations/ember-2.6.3/app/templates/application.hbs
Executable file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<section id="todoapp">
|
||||||
|
<header id="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input type="text" id="new-todo" class="new-todo" onkeydown={{action 'createTodo'}} placeholder="What needs to be done?" autofocus>
|
||||||
|
</header>
|
||||||
|
{{outlet}}
|
||||||
|
{{#if (gt model.length 0)}}
|
||||||
|
<footer id="footer">
|
||||||
|
<span id="todo-count"><strong>{{remaining.length}}</strong> {{pluralize 'item' remaining.length}} left</span>
|
||||||
|
<ul id="filters">
|
||||||
|
<li>{{#link-to "index" activeClass="selected"}}All{{/link-to}}</li>
|
||||||
|
<li>{{#link-to "active" activeClass="selected"}}Active{{/link-to}}</li>
|
||||||
|
<li>{{#link-to "completed" activeClass="selected"}}Completed{{/link-to}}</li>
|
||||||
|
</ul>
|
||||||
|
{{#if completed.length}}
|
||||||
|
<button id="clear-completed" onclick={{action 'clearCompleted'}}>Clear completed</button>
|
||||||
|
{{/if}}
|
||||||
|
</footer>
|
||||||
|
{{/if}}
|
||||||
|
</section>
|
||||||
|
<footer id="info">
|
||||||
|
<p>Double-click to edit a todo</p>
|
||||||
|
<p>
|
||||||
|
Created by
|
||||||
|
<a href="http://github.com/cibernox">Miguel Camba</a>,
|
||||||
|
<a href="http://github.com/addyosmani">Addy Osmani</a>
|
||||||
|
</p>
|
||||||
|
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
|
||||||
|
</footer>
|
||||||
1
bench/implementations/ember-2.6.3/app/templates/completed.hbs
Executable file
1
bench/implementations/ember-2.6.3/app/templates/completed.hbs
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
{{todo-list todos=todos}}
|
||||||
6
bench/implementations/ember-2.6.3/app/templates/components/todo-item.hbs
Executable file
6
bench/implementations/ember-2.6.3/app/templates/components/todo-item.hbs
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div class="view">
|
||||||
|
<input type="checkbox" class="toggle" checked={{todo.completed}} onchange={{action 'toggleCompleted'}}>
|
||||||
|
<label ondblclick={{action 'startEditing'}}>{{todo.title}}</label>
|
||||||
|
<button onclick={{action 'removeTodo'}} class="destroy"></button>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="edit" value={{todo.title}} onblur={{action 'doneEditing' value='target.value'}} onkeydown={{action 'handleKeydown'}} autofocus>
|
||||||
10
bench/implementations/ember-2.6.3/app/templates/components/todo-list.hbs
Executable file
10
bench/implementations/ember-2.6.3/app/templates/components/todo-list.hbs
Executable file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{{#if todos.length}}
|
||||||
|
{{#if canToggle}}
|
||||||
|
<input type="checkbox" id="toggle-all" checked={{allCompleted}} onchange={{action 'toggleAll'}}>
|
||||||
|
{{/if}}
|
||||||
|
<ul id="todo-list" class="todo-list">
|
||||||
|
{{#each todos as |todo|}}
|
||||||
|
{{todo-item todo=todo onStartEdit=(action 'disableToggle') onEndEdit=(action 'enableToggle')}}
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{/if}}
|
||||||
3
bench/implementations/ember-2.6.3/app/templates/index.hbs
Executable file
3
bench/implementations/ember-2.6.3/app/templates/index.hbs
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{#if model.length}}
|
||||||
|
{{todo-list todos=model}}
|
||||||
|
{{/if}}
|
||||||
9
bench/implementations/ember-2.6.3/bower.json
Executable file
9
bench/implementations/ember-2.6.3/bower.json
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "todomvc",
|
||||||
|
"dependencies": {
|
||||||
|
"ember": "~2.6.2",
|
||||||
|
"ember-cli-shims": "0.1.1",
|
||||||
|
"ember-cli-test-loader": "0.2.2",
|
||||||
|
"ember-qunit-notifications": "0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
47
bench/implementations/ember-2.6.3/config/environment.js
Executable file
47
bench/implementations/ember-2.6.3/config/environment.js
Executable file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* jshint node: true */
|
||||||
|
|
||||||
|
module.exports = function (environment) {
|
||||||
|
var ENV = {
|
||||||
|
modulePrefix: 'todomvc',
|
||||||
|
environment: environment,
|
||||||
|
baseURL: null,
|
||||||
|
locationType: 'none',
|
||||||
|
EmberENV: {
|
||||||
|
FEATURES: {
|
||||||
|
// Here you can enable experimental features on an ember canary build
|
||||||
|
// e.g. 'with-controller': true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
APP: {
|
||||||
|
// Here you can pass flags/options to your application instance
|
||||||
|
// when it is created
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// if (environment === 'development') {
|
||||||
|
// ENV.APP.LOG_RESOLVER = true;
|
||||||
|
// ENV.APP.LOG_ACTIVE_GENERATION = true;
|
||||||
|
// ENV.APP.LOG_TRANSITIONS = true;
|
||||||
|
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
|
||||||
|
// ENV.APP.LOG_VIEW_LOOKUPS = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (environment === 'test') {
|
||||||
|
// Testem prefers this...
|
||||||
|
ENV.baseURL = '/';
|
||||||
|
ENV.locationType = 'none';
|
||||||
|
|
||||||
|
// keep test console output quieter
|
||||||
|
ENV.APP.LOG_ACTIVE_GENERATION = false;
|
||||||
|
ENV.APP.LOG_VIEW_LOOKUPS = false;
|
||||||
|
|
||||||
|
ENV.APP.rootElement = '#ember-testing';
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (environment === 'production') {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
return ENV;
|
||||||
|
};
|
||||||
1
bench/implementations/ember-2.6.3/dist/assets/todomvc-b88bd0267dade133c112b65a28b90916.js
vendored
Normal file
1
bench/implementations/ember-2.6.3/dist/assets/todomvc-b88bd0267dade133c112b65a28b90916.js
vendored
Normal file
File diff suppressed because one or more lines are too long
0
bench/implementations/ember-2.6.3/dist/assets/todomvc-d41d8cd98f00b204e9800998ecf8427e.css
vendored
Normal file
0
bench/implementations/ember-2.6.3/dist/assets/todomvc-d41d8cd98f00b204e9800998ecf8427e.css
vendored
Normal file
18
bench/implementations/ember-2.6.3/dist/assets/vendor-01de4edc8253c2828fa6f36d3b1ca849.js
vendored
Normal file
18
bench/implementations/ember-2.6.3/dist/assets/vendor-01de4edc8253c2828fa6f36d3b1ca849.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
bench/implementations/ember-2.6.3/dist/assets/vendor-7b5c98520910afa58d74e05ec86cd873.css
vendored
Normal file
1
bench/implementations/ember-2.6.3/dist/assets/vendor-7b5c98520910afa58d74e05ec86cd873.css
vendored
Normal file
File diff suppressed because one or more lines are too long
15
bench/implementations/ember-2.6.3/dist/crossdomain.xml
vendored
Executable file
15
bench/implementations/ember-2.6.3/dist/crossdomain.xml
vendored
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
|
||||||
|
<cross-domain-policy>
|
||||||
|
<!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html -->
|
||||||
|
|
||||||
|
<!-- Most restrictive policy: -->
|
||||||
|
<site-control permitted-cross-domain-policies="none"/>
|
||||||
|
|
||||||
|
<!-- Least restrictive policy: -->
|
||||||
|
<!--
|
||||||
|
<site-control permitted-cross-domain-policies="all"/>
|
||||||
|
<allow-access-from domain="*" to-ports="*" secure="false"/>
|
||||||
|
<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
|
||||||
|
-->
|
||||||
|
</cross-domain-policy>
|
||||||
25
bench/implementations/ember-2.6.3/dist/index.html
vendored
Normal file
25
bench/implementations/ember-2.6.3/dist/index.html
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<title>Todomvc</title>
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<meta name="todomvc/config/environment" content="%7B%22modulePrefix%22%3A%22todomvc%22%2C%22environment%22%3A%22production%22%2C%22baseURL%22%3Anull%2C%22locationType%22%3A%22none%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%7D%2C%22APP%22%3A%7B%22name%22%3A%22todomvc%22%2C%22version%22%3A%220.0.0+9b29a440%22%7D%2C%22exportApplicationGlobal%22%3Afalse%7D" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="assets/vendor-7b5c98520910afa58d74e05ec86cd873.css" integrity="sha256-bsagGHduhay9QPLUFpddcZFq7Kmr2ScM3VKnWhdX8oM= sha512-eNsGN2aLecWPvoqNVH8oXK8o/IJ7rO5ti0zgS8lF8LiwmKUHdEIuFduwcDL1VLAt2r+3YjgDzoSNYK6c57pJzw==" >
|
||||||
|
<link rel="stylesheet" href="assets/todomvc-d41d8cd98f00b204e9800998ecf8427e.css" integrity="sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= sha512-z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==" >
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="assets/vendor-01de4edc8253c2828fa6f36d3b1ca849.js" integrity="sha256-lBRb2M2secp4ojcJ0xM1d9YkRX7hKTz8KeKZlAWyh6Y= sha512-MfYfzPGAnzELmWDdNiIq7OMBhv2gLn5rwfQ9QtIcxibX0kdu21LLbuEv6RV7eK75XS5WHxksS0R2Xhghfl767A==" ></script>
|
||||||
|
<script src="assets/todomvc-b88bd0267dade133c112b65a28b90916.js" integrity="sha256-KmQwQXUtlnv5yK0MUO7F9qBoMauvZRkmuFO7VuiGoso= sha512-gtby61qJDGevaGdmHBxy1zA40OietcAdgZbUWgEuWhLqnnbkdcEbmkcx1VtYCVrGO63n2ehSzqnl0uFOiSsCqg==" ></script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
bench/implementations/ember-2.6.3/dist/robots.txt
vendored
Executable file
3
bench/implementations/ember-2.6.3/dist/robots.txt
vendored
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# http://www.robotstxt.org
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
26
bench/implementations/ember-2.6.3/ember-cli-build.js
Executable file
26
bench/implementations/ember-2.6.3/ember-cli-build.js
Executable file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*jshint node:true*/
|
||||||
|
/* global require, module */
|
||||||
|
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
|
||||||
|
|
||||||
|
module.exports = function (defaults) {
|
||||||
|
var app = new EmberApp(defaults, {
|
||||||
|
// Add options here
|
||||||
|
});
|
||||||
|
|
||||||
|
app.import('vendor/base.css');
|
||||||
|
app.import('vendor/index.css');
|
||||||
|
// Use `app.import` to add additional libraries to the generated
|
||||||
|
// output files.
|
||||||
|
//
|
||||||
|
// If you need to use different assets in different
|
||||||
|
// environments, specify an object as the first parameter. That
|
||||||
|
// object's keys should be the environment name and the values
|
||||||
|
// should be the asset to use in that environment.
|
||||||
|
//
|
||||||
|
// If the library that you are including contains AMD or ES6
|
||||||
|
// modules that you would like to import into your application
|
||||||
|
// please specify an object with the list of modules as keys
|
||||||
|
// along with the exports of each module as its value.
|
||||||
|
|
||||||
|
return app.toTree();
|
||||||
|
};
|
||||||
44
bench/implementations/ember-2.6.3/package.json
Executable file
44
bench/implementations/ember-2.6.3/package.json
Executable file
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"name": "todomvc",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Small description for todomvc goes here",
|
||||||
|
"private": true,
|
||||||
|
"directories": {
|
||||||
|
"doc": "doc",
|
||||||
|
"test": "tests"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "ember build",
|
||||||
|
"start": "ember server",
|
||||||
|
"test": "ember test",
|
||||||
|
"make": "bower install && ember build -prod"
|
||||||
|
},
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10.0"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"bower": "*",
|
||||||
|
"broccoli-asset-rev": "^2.4.2",
|
||||||
|
"ember-ajax": "^2.0.1",
|
||||||
|
"ember-cli": "2.6.3",
|
||||||
|
"ember-cli-app-version": "^1.0.0",
|
||||||
|
"ember-cli-babel": "^5.1.6",
|
||||||
|
"ember-cli-dependency-checker": "^1.2.0",
|
||||||
|
"ember-cli-htmlbars": "^1.0.3",
|
||||||
|
"ember-cli-htmlbars-inline-precompile": "^0.3.1",
|
||||||
|
"ember-cli-inject-live-reload": "^1.4.0",
|
||||||
|
"ember-cli-jshint": "^1.0.0",
|
||||||
|
"ember-cli-qunit": "^1.4.0",
|
||||||
|
"ember-cli-release": "^0.2.9",
|
||||||
|
"ember-cli-sri": "^2.1.0",
|
||||||
|
"ember-cli-uglify": "^1.2.0",
|
||||||
|
"ember-export-application-global": "^1.0.5",
|
||||||
|
"ember-inflector": "1.9.4",
|
||||||
|
"ember-load-initializers": "^0.5.1",
|
||||||
|
"ember-resolver": "^2.0.3",
|
||||||
|
"loader.js": "^4.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
bench/implementations/ember-2.6.3/public/crossdomain.xml
Executable file
15
bench/implementations/ember-2.6.3/public/crossdomain.xml
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
|
||||||
|
<cross-domain-policy>
|
||||||
|
<!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html -->
|
||||||
|
|
||||||
|
<!-- Most restrictive policy: -->
|
||||||
|
<site-control permitted-cross-domain-policies="none"/>
|
||||||
|
|
||||||
|
<!-- Least restrictive policy: -->
|
||||||
|
<!--
|
||||||
|
<site-control permitted-cross-domain-policies="all"/>
|
||||||
|
<allow-access-from domain="*" to-ports="*" secure="false"/>
|
||||||
|
<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
|
||||||
|
-->
|
||||||
|
</cross-domain-policy>
|
||||||
3
bench/implementations/ember-2.6.3/public/robots.txt
Executable file
3
bench/implementations/ember-2.6.3/public/robots.txt
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# http://www.robotstxt.org
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
13
bench/implementations/ember-2.6.3/testem.js
Executable file
13
bench/implementations/ember-2.6.3/testem.js
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*jshint node:true*/
|
||||||
|
module.exports = {
|
||||||
|
framework: 'qunit',
|
||||||
|
test_page: 'tests/index.html?hidepassed',
|
||||||
|
disable_watching: true,
|
||||||
|
launch_in_ci: [
|
||||||
|
'PhantomJS'
|
||||||
|
],
|
||||||
|
launch_in_dev: [
|
||||||
|
'PhantomJS',
|
||||||
|
'Chrome'
|
||||||
|
]
|
||||||
|
};
|
||||||
141
bench/implementations/ember-2.6.3/vendor/base.css
vendored
Executable file
141
bench/implementations/ember-2.6.3/vendor/base.css
vendored
Executable file
|
|
@ -0,0 +1,141 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
378
bench/implementations/ember-2.6.3/vendor/index.css
vendored
Executable file
378
bench/implementations/ember-2.6.3/vendor/index.css
vendored
Executable file
|
|
@ -0,0 +1,378 @@
|
||||||
|
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; /* Mobile Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto 0;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hack to remove background from Mobile Safari.
|
||||||
|
Can't use it globally since it destroys checkboxes in Firefox
|
||||||
|
*/
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
3248
bench/implementations/inferno/dist/bundle.js
vendored
Normal file
3248
bench/implementations/inferno/dist/bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
20
bench/implementations/inferno/index.html
Normal file
20
bench/implementations/inferno/index.html
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en" data-framework="inferno">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Inferno • TodoMVC</title>
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
|
||||||
|
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section id="app" class="todoapp"></section>
|
||||||
|
<footer class="info">
|
||||||
|
<p>Double-click to edit a todo</p>
|
||||||
|
<p>Created by <a href="http://github.com/lukeed/">Luke Edwards</a></p>
|
||||||
|
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="node_modules/todomvc-common/base.js"></script>
|
||||||
|
<script src="dist/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
bench/implementations/inferno/package.json
Normal file
24
bench/implementations/inferno/package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "webpack-dev-server",
|
||||||
|
"build": "webpack -p",
|
||||||
|
"test": "cd test && npm test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"inferno": "beta32",
|
||||||
|
"inferno-component": "beta32",
|
||||||
|
"todomvc-app-css": "^2.0.0",
|
||||||
|
"todomvc-common": "^1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-core": "^6.5.2",
|
||||||
|
"babel-loader": "^6.2.1",
|
||||||
|
"babel-plugin-inferno": "beta13",
|
||||||
|
"babel-preset-es2015": "^6.5.0",
|
||||||
|
"babel-preset-es2015-loose": "^7.0.0",
|
||||||
|
"babel-preset-stage-0": "^6.5.0",
|
||||||
|
"webpack": "^1.12.14",
|
||||||
|
"webpack-dev-server": "^1.14.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
49
bench/implementations/inferno/src/base.js
Normal file
49
bench/implementations/inferno/src/base.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import Inferno from 'inferno';
|
||||||
|
import { states } from './share';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stateless Header component
|
||||||
|
*/
|
||||||
|
export function Head({onEnter}) {
|
||||||
|
return (
|
||||||
|
<header className="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input className="new-todo" autofocus onkeydown={ onEnter }
|
||||||
|
autocomplete="off" placeholder="What needs to be done?"
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const links = [
|
||||||
|
{hash: '#/', name: 'All'},
|
||||||
|
{hash: '#/active', name: 'Active'},
|
||||||
|
{hash: '#/completed', name: 'Completed'}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stateless Footer component
|
||||||
|
*/
|
||||||
|
export function Foot({left, done, route, onClear}) {
|
||||||
|
return (
|
||||||
|
<footer className="footer">
|
||||||
|
<span className="todo-count">
|
||||||
|
<strong>{ left }</strong> { left > 1 ? 'items' : 'item' } left
|
||||||
|
</span>
|
||||||
|
<ul className="filters">
|
||||||
|
{
|
||||||
|
links.map(({hash, name}) => (
|
||||||
|
<li>
|
||||||
|
<a href={ hash } className={ name.toLowerCase() === route ? 'selected' : '' }>
|
||||||
|
{ name }
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
{ done > 0 ? (
|
||||||
|
<button className="clear-completed" onClick={ onClear }>Clear completed</button>
|
||||||
|
) : null }
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
112
bench/implementations/inferno/src/index.js
Normal file
112
bench/implementations/inferno/src/index.js
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import Inferno from 'inferno';
|
||||||
|
import Component from 'inferno-component';
|
||||||
|
import { ENTER, filters, read } from './share';
|
||||||
|
import { Head, Foot } from './base';
|
||||||
|
import Model from './model';
|
||||||
|
import Item from './item';
|
||||||
|
|
||||||
|
const { render } = Inferno;
|
||||||
|
const model = new Model();
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
state = {
|
||||||
|
route: read(),
|
||||||
|
todos: model.get()
|
||||||
|
};
|
||||||
|
|
||||||
|
update = arr => this.setState({todos: arr});
|
||||||
|
|
||||||
|
componentWillMount = () => {
|
||||||
|
window.onhashchange = () => this.setState({route: read()});
|
||||||
|
};
|
||||||
|
|
||||||
|
add = e => {
|
||||||
|
if (e.which !== ENTER) return;
|
||||||
|
|
||||||
|
const val = e.target.value.trim();
|
||||||
|
if (!val) return;
|
||||||
|
|
||||||
|
e.target.value = '';
|
||||||
|
this.update(
|
||||||
|
model.add(val)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
edit = (todo, val) => {
|
||||||
|
val = val.trim();
|
||||||
|
if (val.length) {
|
||||||
|
this.update(
|
||||||
|
model.put(todo, {title: val, editing: 0})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.remove(todo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
focus = todo => this.update(
|
||||||
|
model.put(todo, {editing: 1})
|
||||||
|
);
|
||||||
|
|
||||||
|
blur = todo => this.update(
|
||||||
|
model.put(todo, {editing: 0})
|
||||||
|
);
|
||||||
|
|
||||||
|
remove = todo => this.update(
|
||||||
|
model.del(todo)
|
||||||
|
);
|
||||||
|
|
||||||
|
toggleOne = todo => this.update(
|
||||||
|
model.toggle(todo)
|
||||||
|
);
|
||||||
|
|
||||||
|
toggleAll = ev => this.update(
|
||||||
|
model.toggleAll(ev.target.checked)
|
||||||
|
);
|
||||||
|
|
||||||
|
clearCompleted = () => this.update(
|
||||||
|
model.clearCompleted()
|
||||||
|
);
|
||||||
|
|
||||||
|
render(_, {todos, route}) {
|
||||||
|
const num = todos.length;
|
||||||
|
const shown = todos.filter(filters[route]);
|
||||||
|
const numDone = todos.filter(filters.completed).length;
|
||||||
|
const numAct = num - numDone;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Head onEnter={ this.add } />
|
||||||
|
|
||||||
|
{ num ? (
|
||||||
|
<section className="main">
|
||||||
|
<input className="toggle-all" type="checkbox"
|
||||||
|
onClick={ this.toggleAll } checked={ numAct === 0 }
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ul className="todo-list">
|
||||||
|
{
|
||||||
|
shown.map(t =>
|
||||||
|
<Item data={t}
|
||||||
|
onBlur={ () => this.blur(t) }
|
||||||
|
onFocus={ () => this.focus(t) }
|
||||||
|
doDelete={ () => this.remove(t) }
|
||||||
|
doSave={ val => this.edit(t, val) }
|
||||||
|
doToggle={ () => this.toggleOne(t) }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
) : null }
|
||||||
|
|
||||||
|
{ (numAct || numDone) ? (
|
||||||
|
<Foot onClear={ this.clearCompleted }
|
||||||
|
left={numAct} done={numDone} route={route}
|
||||||
|
/>
|
||||||
|
) : null }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(<App />, document.getElementById('app'));
|
||||||
58
bench/implementations/inferno/src/item.js
Normal file
58
bench/implementations/inferno/src/item.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import Inferno from 'inferno';
|
||||||
|
import Component from 'inferno-component';
|
||||||
|
import { ESCAPE, ENTER, isEqual } from './share';
|
||||||
|
|
||||||
|
export default class Item extends Component {
|
||||||
|
constructor({data, ...props}) {
|
||||||
|
super(props);
|
||||||
|
this.todo = data;
|
||||||
|
this.state = {text: data.title};
|
||||||
|
this.editor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps = ({data}) => this.setText(data.title);
|
||||||
|
shouldComponentUpdate = ({data}, {text}) => !(isEqual(data, this.todo) && text === this.state.text);
|
||||||
|
componentWillUpdate = ({data}) => (this.todo = data);
|
||||||
|
componentDidUpdate = () => this.editor.focus();
|
||||||
|
|
||||||
|
setText = text => this.setState({text});
|
||||||
|
|
||||||
|
render({doToggle, doDelete, doSave, onBlur, onFocus}, {text}) {
|
||||||
|
const {title, completed, editing} = this.todo;
|
||||||
|
|
||||||
|
const cls = [];
|
||||||
|
editing && cls.push('editing');
|
||||||
|
completed && cls.push('completed');
|
||||||
|
|
||||||
|
const handleKeydown = e => {
|
||||||
|
if (e.which === ESCAPE) return onBlur();
|
||||||
|
if (e.which === ENTER) return doSave(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
// tmp fix
|
||||||
|
const handleBlur = () => doSave(text);
|
||||||
|
const handleInput = e => this.setText(e.target.value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={ cls.join(' ') }>
|
||||||
|
<div className="view">
|
||||||
|
<input className="toggle" type="checkbox"
|
||||||
|
checked={ completed } onClick={ doToggle }
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label ondblclick={ onFocus }>{ title }</label>
|
||||||
|
|
||||||
|
<button className="destroy" onClick={ doDelete }></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input className="edit"
|
||||||
|
ref={el => { this.editor = el }}
|
||||||
|
value={ editing && text }
|
||||||
|
onblur={ handleBlur }
|
||||||
|
oninput={ handleInput }
|
||||||
|
onkeydown={ handleKeydown }
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
bench/implementations/inferno/src/model.js
Normal file
36
bench/implementations/inferno/src/model.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { assign, isEqual } from './share';
|
||||||
|
|
||||||
|
const STOR = window.localStorage;
|
||||||
|
const STOR_ID = 'todos-inferno';
|
||||||
|
|
||||||
|
export default class Model {
|
||||||
|
get = () => (this.data = JSON.parse(STOR.getItem(STOR_ID) || '[]'))
|
||||||
|
|
||||||
|
set = arr => {
|
||||||
|
this.data = arr || this.data || [];
|
||||||
|
STOR.setItem(STOR_ID, JSON.stringify(this.data));
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
add = str => this.set(
|
||||||
|
this.data.concat({title: str, completed: false})
|
||||||
|
)
|
||||||
|
|
||||||
|
put = (todo, obj) => this.set(
|
||||||
|
this.data.map(t => isEqual(t, todo) ? assign(todo, obj) : t)
|
||||||
|
)
|
||||||
|
|
||||||
|
del = todo => this.set(
|
||||||
|
this.data.filter(t => !isEqual(t, todo))
|
||||||
|
)
|
||||||
|
|
||||||
|
toggle = todo => this.put(todo, {completed: !todo.completed})
|
||||||
|
|
||||||
|
toggleAll = completed => this.set(
|
||||||
|
this.data.map(t => ({...t, completed}))
|
||||||
|
)
|
||||||
|
|
||||||
|
clearCompleted = () => this.set(
|
||||||
|
this.data.filter(t => !t.completed)
|
||||||
|
)
|
||||||
|
}
|
||||||
58
bench/implementations/inferno/src/share.js
Normal file
58
bench/implementations/inferno/src/share.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* Shared funcs/values
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const ENTER = 13;
|
||||||
|
export const ESCAPE = 27;
|
||||||
|
|
||||||
|
export const filters = {
|
||||||
|
all: t => true,
|
||||||
|
active: t => !t.completed,
|
||||||
|
completed: t => t.completed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the `location.hash` value
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
export function read() {
|
||||||
|
return location.hash.replace('#/', '') || 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modified `Object.assign` shim
|
||||||
|
* - always writes to new object
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
export function assign() {
|
||||||
|
let src;
|
||||||
|
let tar = {};
|
||||||
|
for (let s = 0; s < arguments.length; s++) {
|
||||||
|
src = Object(arguments[s]);
|
||||||
|
for (const k in src) {
|
||||||
|
tar[k] = src[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are two Objects equal values?
|
||||||
|
* @param {Object} a
|
||||||
|
* @param {Object} b
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
export function isEqual(a, b) {
|
||||||
|
// Create arrays of property names
|
||||||
|
const aProps = Object.getOwnPropertyNames(a);
|
||||||
|
const bProps = Object.getOwnPropertyNames(b);
|
||||||
|
|
||||||
|
if (aProps.length !== bProps.length) return false;
|
||||||
|
|
||||||
|
for (let i = 0; i < aProps.length; i++) {
|
||||||
|
const k = aProps[i];
|
||||||
|
if (a[k] !== b[k]) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
2
bench/implementations/inferno/test/.gitignore
vendored
Normal file
2
bench/implementations/inferno/test/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
189
bench/implementations/inferno/test/README.md
Normal file
189
bench/implementations/inferno/test/README.md
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
# TodoMVC Browser Tests
|
||||||
|
|
||||||
|
The TodoMVC project has a great many implementations of exactly the same app using different MV* frameworks. Each app should be functionally identical. The goal of these tests is to provide a fully automated browser-based test that can be used to ensure that the specification is being followed by each and every TodoMVC app.
|
||||||
|
|
||||||
|
|
||||||
|
## Todo
|
||||||
|
|
||||||
|
- [ ] Complete the test implementation (27 out of 28 are now complete). The only test that I am struggling with is to test that the delete button becomes visible on hover.
|
||||||
|
- [ ] Make it work with PhantomJS. In practice, Phantom is only a little bit faster, but it should work. Currently there are a few Phantom specific failures.
|
||||||
|
- [ ] Allow testing of apps that require a server (see: https://github.com/tastejs/todomvc/pull/821/files#r9377070)
|
||||||
|
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
These tests use Selenium 2 (WebDriver), via the JavaScript API (WebdriverJS). In order to run the tests, you will need to install the dependencies. npm must be version 2.0.0 or greater, so upgrade it first with `npm install -g npm` if `npm --version` outputs anything less than 2.0.0. Run the following command from within the `tests` folder:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
If you haven't already run `npm install` in the root directory, execute `npm install` there as well.
|
||||||
|
|
||||||
|
You need to run a local server for the tests. Start the test server using:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ gulp test-server
|
||||||
|
```
|
||||||
|
|
||||||
|
To run the tests for all TodoMVC implementations, run the following:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to run tests for a single TodoMVC implementation, supply a framework argument as follows:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm run test -- --framework=angularjs
|
||||||
|
```
|
||||||
|
|
||||||
|
N.B. Remember the extra -- to separate the script arguments from the npm arguments.
|
||||||
|
|
||||||
|
In order to run a specific test, use the mocha 'grep' function. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm run test -- --framework=jquery --grep 'should trim entered text'
|
||||||
|
|
||||||
|
TodoMVC - jquery
|
||||||
|
Editing
|
||||||
|
✓ should trim entered text (1115ms)
|
||||||
|
|
||||||
|
|
||||||
|
1 passing (3s)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specifying the browser
|
||||||
|
|
||||||
|
You can also specify the browser that will be used to execute the tests via the `---browser` argument. The tests default to using Chrome. For example, to run against phantomjs, use the following:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm run test -- --browser=phantomjs
|
||||||
|
```
|
||||||
|
|
||||||
|
You must install phantomjs first of course!
|
||||||
|
|
||||||
|
Valid browser names can be found within webdriver via the `webdriver.Browser` enumeration.
|
||||||
|
|
||||||
|
|
||||||
|
## Reporting against known issues
|
||||||
|
|
||||||
|
The `knownIssues.js` file details the currently known issues with the TodoMVC implementations. You can run the tests and compare against these issues using the `mocha-known-issues-reporter`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm run test -- --reporter=mocha-known-issues-reporter
|
||||||
|
```
|
||||||
|
|
||||||
|
When run via grunt the suite supports exactly the same command line arguments.
|
||||||
|
|
||||||
|
An example output with the known issues reporter is shown below:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm run test -- --reporter=mocha-known-issues-reporter --framework=jquery
|
||||||
|
...
|
||||||
|
Running "simplemocha:files" (simplemocha) task
|
||||||
|
(1 of 27) pass: TodoMVC - jquery, No Todos, should hide #main and #footer
|
||||||
|
[...]
|
||||||
|
(17 of 27) pass: TodoMVC - jquery, Editing, should remove the item if an empty text string was entered
|
||||||
|
(18 of 27) known issue: TodoMVC - jquery, Editing, should cancel edits on escape -- error: undefined
|
||||||
|
(19 of 27) pass: TodoMVC - jquery, Counter, should display the current number of todo items
|
||||||
|
(20 of 27) pass: TodoMVC - jquery, Clear completed button, should display the number of completed items
|
||||||
|
(21 of 27) pass: TodoMVC - jquery, Clear completed button, should remove completed items when clicked
|
||||||
|
(22 of 27) pass: TodoMVC - jquery, Clear completed button, should be hidden when there are no items that are completed
|
||||||
|
(23 of 27) pass: TodoMVC - jquery, Persistence, should persist its data
|
||||||
|
(24 of 27) known issue: TodoMVC - jquery, Routing, should allow me to display active items -- error: Cannot call method 'click' of undefined
|
||||||
|
(25 of 27) known issue: TodoMVC - jquery, Routing, should allow me to display completed items -- error: Cannot call method 'click' of undefined
|
||||||
|
(26 of 27) known issue: TodoMVC - jquery, Routing, should allow me to display all items -- error: Cannot call method 'click' of undefined
|
||||||
|
(27 of 27) known issue: TodoMVC - jquery, Routing, should highlight the currently applied filter -- error: Cannot call method 'getAttribute' of undefined
|
||||||
|
|
||||||
|
passed: 22/27
|
||||||
|
failed: 5/27
|
||||||
|
new issues: 0
|
||||||
|
resolved issues: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
The reporter indicates the number of passes, failed, new and resolved issues. This makes it ideal for regression testing.
|
||||||
|
|
||||||
|
### Example output
|
||||||
|
|
||||||
|
A test run with the 'spec' reporter looks something like the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm run test -- --framework=angularjs
|
||||||
|
|
||||||
|
angularjs
|
||||||
|
TodoMVC
|
||||||
|
No Todos
|
||||||
|
✓ should hide #main and #footer (201ms)
|
||||||
|
New Todo
|
||||||
|
✓ should allow me to add todo items (548ms)
|
||||||
|
✓ should clear text input field when an item is added (306ms)
|
||||||
|
✓ should trim text input (569ms)
|
||||||
|
✓ should show #main and #footer when items added (405ms)
|
||||||
|
Mark all as completed
|
||||||
|
✓ should allow me to mark all items as completed (1040ms)
|
||||||
|
✓ should allow me to clear the completion state of all items (1014ms)
|
||||||
|
✓ complete all checkbox should update state when items are completed (1413ms)
|
||||||
|
Item
|
||||||
|
✓ should allow me to mark items as complete (843ms)
|
||||||
|
✓ should allow me to un-mark items as complete (978ms)
|
||||||
|
✓ should allow me to edit an item (1155ms)
|
||||||
|
✓ should show the remove button on hover
|
||||||
|
Editing
|
||||||
|
✓ should hide other controls when editing (718ms)
|
||||||
|
✓ should save edits on enter (1093ms)
|
||||||
|
✓ should save edits on blur (1256ms)
|
||||||
|
✓ should trim entered text (1163ms)
|
||||||
|
✓ should remove the item if an empty text string was entered (1033ms)
|
||||||
|
✓ should cancel edits on escape (1115ms)
|
||||||
|
Counter
|
||||||
|
✓ should display the current number of todo items (462ms)
|
||||||
|
Clear completed button
|
||||||
|
✓ should display the number of completed items (873ms)
|
||||||
|
✓ should remove completed items when clicked (898ms)
|
||||||
|
✓ should be hidden when there are no items that are completed (893ms)
|
||||||
|
Persistence
|
||||||
|
✓ should persist its data (3832ms)
|
||||||
|
Routing
|
||||||
|
✓ should allow me to display active items (871ms)
|
||||||
|
✓ should allow me to display completed items (960ms)
|
||||||
|
✓ should allow me to display all items (1192ms)
|
||||||
|
✓ should highlight the currently applied filter (1095ms)
|
||||||
|
|
||||||
|
|
||||||
|
27 passing (1m)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Speed mode
|
||||||
|
|
||||||
|
In order to keep each test case fully isolated, the browser is closed then re-opened in between each test. This does mean that the tests can take quite a long time to run. If you don't mind the risk of side-effects you can run the tests in speed mode by adding the `--speedMode` argument.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm run test -- --speedMode
|
||||||
|
```
|
||||||
|
|
||||||
|
Before each test, all the todo items are checked as completed and the 'clear complete' button pressed. This makes the tests run in around half the time, but with the obvious risk that the tear-down code may fail.
|
||||||
|
|
||||||
|
|
||||||
|
## Lax mode
|
||||||
|
|
||||||
|
There are certain implementations (e.g. GWT and Dojo) where the constraints of the framework mean that it is not possible to match exactly the HTML specification for TodoMVC. In these cases, the tests can be run in a 'lax' mode where the XPath queries used to locate DOM elements are more general. For example, rather than looking for a checkbox `input` element with a class of `toggle`, in lax mode it simply looks for any `input` elements of type `checkbox`. To run the tests in lax mode, simply use the `--laxMode` argument:
|
||||||
|
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ npm run test -- --laxMode
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Test design
|
||||||
|
|
||||||
|
Very briefly, the tests are designed as follows:
|
||||||
|
|
||||||
|
- `page.js` - provides an abstraction layer for the HTML template. All the code required to access elements from the DOM is found within this file. The XPaths used to locate elements are based on the TodoMVC specification, using the required element classes / ids.
|
||||||
|
- `pageLaxMode.js` - extends the above in order to relax the XPath constraints.
|
||||||
|
- `testOperations.js` - provides common assertions and operations.
|
||||||
|
- `test.js` - Erm … the tests! These are written to closely match the TodoMVC spec.
|
||||||
|
- `allTest.js` - A simple file that locates all of the framework examples, and runs the tests for each.
|
||||||
|
|
||||||
|
**NOTE:** All of the WebdriverJS methods return promises and are executed asynchronously. However, you do not have to 'chain' them using `then`, they are instead automagically added to a queue, then executed. This means that if you add non-WebdriverJS operations (asserts, log messages) these will not be executed at the point you might expect. This is why `TestOperations.js` uses an explicit `then` each time it asserts.
|
||||||
414
bench/implementations/inferno/test/index.js
Normal file
414
bench/implementations/inferno/test/index.js
Normal file
|
|
@ -0,0 +1,414 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var webdriver = require('selenium-webdriver');
|
||||||
|
var chrome = require('selenium-webdriver/chrome');
|
||||||
|
var test = require('selenium-webdriver/testing');
|
||||||
|
var Page = require('./page');
|
||||||
|
var PageLaxMode = require('./pageLaxMode');
|
||||||
|
var TestOperations = require('./testOperations');
|
||||||
|
|
||||||
|
var argv = require('optimist').default('laxMode', false).default('browser', 'chrome').argv;
|
||||||
|
|
||||||
|
function todoMVCTest(baseUrl, speedMode, laxMode, browserName) {
|
||||||
|
test.describe('TodoMVC: ', function () {
|
||||||
|
|
||||||
|
var TODO_ITEM_ONE = 'buy some cheese';
|
||||||
|
var TODO_ITEM_TWO = 'feed the cat';
|
||||||
|
var TODO_ITEM_THREE = 'book a doctors appointment';
|
||||||
|
var browser, testOps, page;
|
||||||
|
|
||||||
|
// a number of tests use this set of ToDo items.
|
||||||
|
function createStandardItems(done) {
|
||||||
|
page.enterItem(TODO_ITEM_ONE);
|
||||||
|
page.enterItem(TODO_ITEM_TWO);
|
||||||
|
return page.enterItem(TODO_ITEM_THREE)
|
||||||
|
.then(function () {
|
||||||
|
if (done instanceof Function) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function launchBrowser(done) {
|
||||||
|
var chromeOptions = new chrome.Options();
|
||||||
|
chromeOptions.addArguments('no-sandbox');
|
||||||
|
|
||||||
|
if (process.env.CHROME_PATH !== undefined) {
|
||||||
|
chromeOptions.setChromeBinaryPath(process.env.CHROME_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
browser = new webdriver.Builder()
|
||||||
|
.withCapabilities({browserName: browserName})
|
||||||
|
.setChromeOptions(chromeOptions)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
browser.get(baseUrl);
|
||||||
|
|
||||||
|
page = laxMode ? new PageLaxMode(browser) : new Page(browser);
|
||||||
|
testOps = new TestOperations(page);
|
||||||
|
|
||||||
|
return page.ensureAppIsVisibleAndLoaded()
|
||||||
|
.then(function () {
|
||||||
|
if (done instanceof Function) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function printCapturedLogs() {
|
||||||
|
var logs = browser.manage().logs();
|
||||||
|
|
||||||
|
return logs.get('browser')
|
||||||
|
.then(function (entries) {
|
||||||
|
if (entries && entries.length) {
|
||||||
|
console.log(entries);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBrowser(done) {
|
||||||
|
return browser
|
||||||
|
.quit()
|
||||||
|
.then(function () {
|
||||||
|
if (done instanceof Function) { done(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (speedMode) {
|
||||||
|
test.before(launchBrowser);
|
||||||
|
test.after(closeBrowser);
|
||||||
|
test.afterEach(function (done) {
|
||||||
|
return browser.executeScript('window.localStorage && localStorage.clear(); location.reload(true);')
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
test.beforeEach(launchBrowser);
|
||||||
|
test.afterEach(function (done) {
|
||||||
|
printCapturedLogs()
|
||||||
|
.then(function () {
|
||||||
|
return closeBrowser(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe('When page is initially opened', function () {
|
||||||
|
|
||||||
|
test.it('should focus on the todo input field', function (done) {
|
||||||
|
testOps.assertNewInputFocused()
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('No Todos', function () {
|
||||||
|
|
||||||
|
test.it('should hide #main and #footer', function (done) {
|
||||||
|
testOps.assertItemCount(0);
|
||||||
|
testOps.assertMainSectionVisibility(false);
|
||||||
|
testOps.assertFooterVisibility(false)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('New Todo', function () {
|
||||||
|
|
||||||
|
test.it('should allow me to add todo items', function (done) {
|
||||||
|
page.enterItem(TODO_ITEM_ONE);
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE]);
|
||||||
|
page.enterItem(TODO_ITEM_TWO);
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should clear text input field when an item is added', function (done) {
|
||||||
|
page.enterItem(TODO_ITEM_ONE);
|
||||||
|
testOps.assertNewItemInputFieldText('')
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should append new items to the bottom of the list', function (done) {
|
||||||
|
createStandardItems();
|
||||||
|
testOps.assertItemCount(3);
|
||||||
|
testOps.assertItemText(0, TODO_ITEM_ONE);
|
||||||
|
testOps.assertItemText(1, TODO_ITEM_TWO);
|
||||||
|
testOps.assertItemText(2, TODO_ITEM_THREE)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should trim text input', function (done) {
|
||||||
|
page.enterItem(' ' + TODO_ITEM_ONE + ' ');
|
||||||
|
testOps.assertItemText(0, TODO_ITEM_ONE)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should show #main and #footer when items added', function (done) {
|
||||||
|
page.enterItem(TODO_ITEM_ONE);
|
||||||
|
testOps.assertMainSectionVisibility(true);
|
||||||
|
testOps.assertFooterVisibility(true)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Mark all as completed', function () {
|
||||||
|
|
||||||
|
test.beforeEach(createStandardItems);
|
||||||
|
|
||||||
|
test.it('should allow me to mark all items as completed', function (done) {
|
||||||
|
page.clickMarkAllCompletedCheckBox();
|
||||||
|
testOps.assertItemCompletedStates([true, true, true])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should correctly update the complete all checked state', function (done) {
|
||||||
|
// manually check all items
|
||||||
|
page.toggleItemAtIndex(0);
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
page.toggleItemAtIndex(2);
|
||||||
|
|
||||||
|
// ensure checkall is in the correct state
|
||||||
|
testOps.assertCompleteAllCheckedStatus(true)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should allow me to clear the completion state of all items', function (done) {
|
||||||
|
page.clickMarkAllCompletedCheckBox();
|
||||||
|
page.clickMarkAllCompletedCheckBox();
|
||||||
|
|
||||||
|
testOps.assertItemCompletedStates([false, false, false])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('complete all checkbox should update state when items are completed / cleared', function (done) {
|
||||||
|
page.clickMarkAllCompletedCheckBox();
|
||||||
|
testOps.assertCompleteAllCheckedStatus(true);
|
||||||
|
|
||||||
|
// all items are complete, now mark one as not-complete
|
||||||
|
page.toggleItemAtIndex(0);
|
||||||
|
testOps.assertCompleteAllCheckedStatus(false);
|
||||||
|
|
||||||
|
// now mark as complete, so that once again all items are completed
|
||||||
|
page.toggleItemAtIndex(0);
|
||||||
|
testOps.assertCompleteAllCheckedStatus(true)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Item', function () {
|
||||||
|
|
||||||
|
test.it('should allow me to mark items as complete', function (done) {
|
||||||
|
page.enterItem(TODO_ITEM_ONE);
|
||||||
|
page.enterItem(TODO_ITEM_TWO);
|
||||||
|
|
||||||
|
page.toggleItemAtIndex(0);
|
||||||
|
testOps.assertItemCompletedStates([true, false]);
|
||||||
|
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
testOps.assertItemCompletedStates([true, true])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should allow me to un-mark items as complete', function (done) {
|
||||||
|
page.enterItem(TODO_ITEM_ONE);
|
||||||
|
page.enterItem(TODO_ITEM_TWO);
|
||||||
|
|
||||||
|
page.toggleItemAtIndex(0);
|
||||||
|
testOps.assertItemCompletedStates([true, false]);
|
||||||
|
|
||||||
|
page.toggleItemAtIndex(0);
|
||||||
|
testOps.assertItemCompletedStates([false, false])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Editing', function () {
|
||||||
|
|
||||||
|
test.beforeEach(function (done) {
|
||||||
|
createStandardItems();
|
||||||
|
page.doubleClickItemAtIndex(1)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should focus the input', function (done) {
|
||||||
|
testOps.assertItemInputFocused();
|
||||||
|
testOps.assertNewInputBlurred() // Unnecessary? The HTML spec dictates that only one element can be focused.
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should hide other controls when editing', function (done) {
|
||||||
|
testOps.assertItemToggleIsHidden(1);
|
||||||
|
testOps.assertItemLabelIsHidden(1)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should save edits on enter', function (done) {
|
||||||
|
page.editItemAtIndex(1, 'buy some sausages' + webdriver.Key.ENTER);
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, 'buy some sausages', TODO_ITEM_THREE])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should save edits on blur', function (done) {
|
||||||
|
page.editItemAtIndex(1, 'buy some sausages');
|
||||||
|
// click a toggle button so that the blur() event is fired
|
||||||
|
page.toggleItemAtIndex(0);
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, 'buy some sausages', TODO_ITEM_THREE])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should trim entered text', function (done) {
|
||||||
|
page.editItemAtIndex(1, ' buy some sausages ' + webdriver.Key.ENTER);
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, 'buy some sausages', TODO_ITEM_THREE])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should remove the item if an empty text string was entered', function (done) {
|
||||||
|
page.editItemAtIndex(1, webdriver.Key.ENTER);
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_THREE])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should cancel edits on escape', function (done) {
|
||||||
|
page.editItemAtIndex(1, 'foo' + webdriver.Key.ESCAPE);
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Counter', function () {
|
||||||
|
|
||||||
|
test.it('should display the current number of todo items', function (done) {
|
||||||
|
page.enterItem(TODO_ITEM_ONE);
|
||||||
|
testOps.assertItemCountText('1 item left');
|
||||||
|
page.enterItem(TODO_ITEM_TWO);
|
||||||
|
testOps.assertItemCountText('2 items left')
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Clear completed button', function () {
|
||||||
|
|
||||||
|
test.beforeEach(createStandardItems);
|
||||||
|
|
||||||
|
test.it('should display the correct text', function (done) {
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
testOps.assertClearCompleteButtonText('Clear completed')
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should remove completed items when clicked', function (done) {
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
page.clickClearCompleteButton();
|
||||||
|
testOps.assertItemCount(2);
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_THREE])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should be hidden when there are no items that are completed', function (done) {
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
testOps.assertClearCompleteButtonVisibility(true);
|
||||||
|
page.clickClearCompleteButton();
|
||||||
|
testOps.assertClearCompleteButtonVisibility(false)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Persistence', function () {
|
||||||
|
|
||||||
|
test.it('should persist its data', function (done) {
|
||||||
|
function stateTest() {
|
||||||
|
testOps.assertItemCount(2);
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO]);
|
||||||
|
return testOps.assertItemCompletedStates([false, true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up state
|
||||||
|
page.enterItem(TODO_ITEM_ONE);
|
||||||
|
page.enterItem(TODO_ITEM_TWO);
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
stateTest();
|
||||||
|
|
||||||
|
// navigate away and back again
|
||||||
|
browser.get('about:blank');
|
||||||
|
browser.get(baseUrl);
|
||||||
|
|
||||||
|
// repeat the state test
|
||||||
|
stateTest()
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Routing', function () {
|
||||||
|
|
||||||
|
test.beforeEach(createStandardItems);
|
||||||
|
|
||||||
|
test.it('should allow me to display active items', function (done) {
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
page.filterByActiveItems();
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, page.ITEM_HIDDEN_OR_REMOVED, TODO_ITEM_THREE])
|
||||||
|
.then(function () { return done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should respect the back button', function (done) {
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
page.filterByActiveItems();
|
||||||
|
page.filterByCompletedItems();
|
||||||
|
testOps.assertItems([page.ITEM_HIDDEN_OR_REMOVED, TODO_ITEM_TWO]); // should show completed items
|
||||||
|
page.back(); // then active items
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, page.ITEM_HIDDEN_OR_REMOVED, TODO_ITEM_THREE]);
|
||||||
|
page.back(); // then all items
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should allow me to display completed items', function (done) {
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
page.filterByCompletedItems();
|
||||||
|
testOps.assertItems([page.ITEM_HIDDEN_OR_REMOVED, TODO_ITEM_TWO]);
|
||||||
|
page.filterByAllItems() // TODO: why
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should allow me to display all items', function (done) {
|
||||||
|
page.toggleItemAtIndex(1);
|
||||||
|
|
||||||
|
// apply the other filters first, before returning to the 'all' state
|
||||||
|
page.filterByActiveItems();
|
||||||
|
page.filterByCompletedItems();
|
||||||
|
page.filterByAllItems();
|
||||||
|
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE])
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.it('should highlight the currently applied filter', function (done) {
|
||||||
|
// initially 'all' should be selected
|
||||||
|
testOps.assertFilterAtIndexIsSelected(0);
|
||||||
|
page.filterByActiveItems();
|
||||||
|
testOps.assertFilterAtIndexIsSelected(1);
|
||||||
|
page.filterByCompletedItems();
|
||||||
|
testOps.assertFilterAtIndexIsSelected(2)
|
||||||
|
.then(function () { done(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init test
|
||||||
|
*/
|
||||||
|
todoMVCTest(
|
||||||
|
'http://localhost:8000/index.html',
|
||||||
|
argv.speedMode,
|
||||||
|
argv.laxMode,
|
||||||
|
argv.browser
|
||||||
|
);
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue