├── .gitignore ├── LICENCE ├── README.md ├── img ├── 2016-05-18.png └── 2016-06-25.png ├── index.html ├── package.json ├── resources ├── benchmark-runner.js ├── manager.js └── tests.js └── todomvc ├── angularjs-perf ├── index.html ├── js │ ├── app.js │ ├── controllers │ │ └── todoCtrl.js │ ├── directives │ │ └── todoFocus.js │ └── services │ │ └── todoStorage.js ├── node_modules │ ├── angular │ │ └── angular.js │ ├── todomvc-app-css │ │ └── index.css │ └── todomvc-common │ │ ├── base.css │ │ └── base.js ├── package.json └── readme.md ├── backbone ├── bower.json ├── bower_components │ ├── backbone.localStorage │ │ └── backbone.localStorage.js │ ├── backbone │ │ └── backbone.js │ ├── jquery │ │ └── jquery.js │ ├── todomvc-common │ │ ├── base.css │ │ ├── base.js │ │ └── bg.png │ └── underscore │ │ └── underscore.js ├── index.html ├── js │ ├── app.js │ ├── collections │ │ └── todos.js │ ├── models │ │ └── todo.js │ ├── routers │ │ └── router.js │ └── views │ │ ├── app-view.js │ │ └── todo-view.js └── readme.md ├── choo ├── dist │ ├── base.css │ ├── index.css │ └── index.js └── index.html ├── elm17 ├── Todo.elm ├── bg.png ├── elm-package.json ├── elm.js ├── index.html └── style.css ├── emberjs ├── bower.json ├── bower_components │ ├── ember-data │ │ └── ember-data.prod.js │ ├── ember-localstorage-adapter │ │ └── localstorage_adapter.js │ ├── ember │ │ └── ember.prod.js │ ├── handlebars │ │ └── handlebars.js │ ├── jquery │ │ └── jquery.js │ └── todomvc-common │ │ ├── base.css │ │ ├── base.js │ │ └── bg.png ├── index.html ├── js │ ├── app.js │ ├── controllers │ │ ├── todo_controller.js │ │ └── todos_controller.js │ ├── helpers │ │ └── pluralize.js │ ├── models │ │ └── todo.js │ ├── router.js │ └── views │ │ ├── edit_todo_view.js │ │ └── todos_view.js └── readme.md ├── knockoutjs ├── .gitignore ├── index.html ├── js │ └── app.js ├── node_modules │ ├── director │ │ └── build │ │ │ └── director.js │ ├── knockout │ │ └── build │ │ │ └── output │ │ │ └── knockout-latest.js │ ├── todomvc-app-css │ │ └── index.css │ └── todomvc-common │ │ ├── base.css │ │ └── base.js ├── package.json └── readme.md ├── mercury ├── bg.png ├── browser.js ├── index.html ├── input.js ├── lib │ ├── do-mutable-focus.js │ └── raf-listen.js ├── render.js ├── state.js ├── style.css └── update.js ├── mithril ├── LICENSE ├── README.md ├── bower.json ├── bower_components │ ├── mithril │ │ ├── .bower.json │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── Gruntfile.js │ │ ├── LICENSE │ │ ├── README.md │ │ ├── background.html │ │ ├── deploy │ │ │ └── cdnjs-package.json │ │ ├── mithril.d.ts │ │ ├── mithril.js │ │ └── package.json │ └── todomvc-common │ │ ├── .bower.json │ │ ├── base.css │ │ ├── base.js │ │ ├── bg.png │ │ ├── bower.json │ │ └── readme.md ├── css │ └── app.css ├── index.html └── js │ ├── app.js │ ├── controllers │ └── todo.js │ ├── models │ └── todo.js │ └── views │ ├── footer-view.js │ ├── main-view.js │ └── single-view.js ├── om ├── app.js ├── bower.json ├── bower_components │ ├── director │ │ ├── .bower.json │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── lib │ │ │ ├── director.js │ │ │ └── director │ │ │ │ ├── browser.js │ │ │ │ ├── cli.js │ │ │ │ ├── http │ │ │ │ ├── index.js │ │ │ │ ├── methods.js │ │ │ │ └── responses.js │ │ │ │ └── router.js │ │ └── package.json │ └── todomvc-common │ │ ├── base.css │ │ ├── base.js │ │ └── bg.png ├── index.html ├── project.clj └── readme.md ├── preact ├── bower_components │ └── todomvc-common │ │ ├── .bower.json │ │ ├── base.css │ │ ├── base.js │ │ ├── bg.png │ │ ├── bower.json │ │ └── readme.md ├── bundle.js ├── bundle.js.map ├── index.html ├── package.json └── src │ ├── app.js │ ├── footer.js │ ├── header.js │ ├── index.js │ ├── item.js │ ├── model.js │ └── util.js ├── ractive ├── bower.json ├── bower_components │ ├── director │ │ └── director.js │ ├── ractive │ │ └── Ractive.js │ └── todomvc-common │ │ ├── base.css │ │ ├── base.js │ │ └── bg.png ├── css │ └── app.css ├── index.html ├── js │ ├── app.js │ ├── persistence.js │ └── routes.js └── readme.md ├── react ├── .gitignore ├── index.html ├── js │ ├── app.js │ ├── app.jsx │ ├── footer.js │ ├── footer.jsx │ ├── todoItem.js │ ├── todoItem.jsx │ ├── todoModel.js │ └── utils.js ├── package.json ├── readme.md ├── todomvc-app-css │ ├── index.css │ ├── package.json │ └── readme.md └── todomvc-common │ ├── base.css │ └── base.js ├── vanilla-es6 ├── .gitignore ├── README.md ├── dist │ ├── app.js │ ├── bundle.js │ ├── controller.js │ ├── helpers.js │ ├── model.js │ ├── store.js │ ├── template.js │ └── view.js ├── index.html ├── node_modules │ ├── todomvc-app-css │ │ └── index.css │ └── todomvc-common │ │ └── base.css ├── package.json └── src │ ├── .jshintrc │ ├── app.js │ ├── controller.js │ ├── helpers.js │ ├── model.js │ ├── store.js │ ├── template.js │ └── view.js └── vue ├── .gitignore ├── index.html ├── js ├── app.js ├── routes.js └── store.js ├── node_modules ├── director │ └── build │ │ └── director.js ├── todomvc-app-css │ └── index.css ├── todomvc-common │ ├── base.css │ └── base.js └── vue │ └── dist │ └── vue.js ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *elm-stuff 2 | *node_modules 3 | *.log 4 | .DS_Store -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Matt Esch. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TodoMVC Performance Benchmark 2 | 3 | [![Sample results for Chrome 51 + OSX 10.11.4 on a Macbook Air](img/2016-06-25.png)](https://rabbots.github.io/todomvc-perf) 4 | 5 | # Try 6 | https://rabbots.github.io/todomvc-perf 7 | 8 | # Setup 9 | ``` 10 | npm run install 11 | ``` 12 | # Develop 13 | ``` 14 | npm run start 15 | ``` 16 | # TODO 17 | - [ ] Collect client info. 18 | - [ ] Average test. 19 | - [ ] Size comparison. 20 | - [ ] Add more framework. 21 | - [ ] DRY test. 22 | - [ ] Better UI/UX. 23 | -------------------------------------------------------------------------------- /img/2016-05-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rabbotio/todomvc-perf/f21ada979b180f5ea67dd1c78dfee3ef91f08bdc/img/2016-05-18.png -------------------------------------------------------------------------------- /img/2016-06-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rabbotio/todomvc-perf/f21ada979b180f5ea67dd1c78dfee3ef91f08bdc/img/2016-06-25.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TodoMVC Benchmark 5 | 6 | 7 | 8 | 24 | 25 | 26 |
27 | 29 |

TodoMVC Benchmark v1.0.0

30 |

31 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-perf-comparison", 3 | "version": "0.0.1", 4 | "description": "Speed FTW", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "opener http://localhost:3000 && http-server -p 3000", 8 | "deploy": "gh-pages -d .", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/rabbots/todomvc-perf-comparison.git" 14 | }, 15 | "keywords": [ 16 | "pref", 17 | "speed", 18 | "todomvc" 19 | ], 20 | "author": "katopz", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/rabbots/todomvc-perf-comparison/issues" 24 | }, 25 | "homepage": "https://github.com/rabbots/todomvc-perf-comparison#readme", 26 | "devDependencies": { 27 | "gh-pages": "^0.11.0", 28 | "http-server": "^0.9.0", 29 | "opener": "^1.4.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /todomvc/angularjs-perf/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularJS • TodoMVC 6 | 7 | 8 | 9 | 10 | 11 |
12 | 18 |
19 | 20 | 21 | 39 |
40 | 57 |
58 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /todomvc/angularjs-perf/js/app.js: -------------------------------------------------------------------------------- 1 | /* jshint undef: true, unused: true */ 2 | /*global angular */ 3 | (function () { 4 | 'use strict'; 5 | 6 | /** 7 | * The main TodoMVC app module that pulls all dependency modules declared in same named files 8 | * 9 | * @type {angular.Module} 10 | */ 11 | angular.module('todomvc', ['todoCtrl', 'todoFocus', 'todoStorage']); 12 | })(); 13 | -------------------------------------------------------------------------------- /todomvc/angularjs-perf/js/controllers/todoCtrl.js: -------------------------------------------------------------------------------- 1 | /* jshint undef: true, unused: true */ 2 | /*global angular */ 3 | 4 | /* 5 | * Line below lets us save `this` as `TC` 6 | * to make properties look exactly the same as in the template 7 | */ 8 | //jscs:disable safeContextKeyword 9 | (function () { 10 | 'use strict'; 11 | 12 | angular.module('todoCtrl', []) 13 | 14 | /** 15 | * The main controller for the app. The controller: 16 | * - retrieves and persists the model via the todoStorage service 17 | * - exposes the model to the template and provides event handlers 18 | */ 19 | .controller('TodoCtrl', function TodoCtrl($scope, $location, todoStorage) { 20 | var TC = this; 21 | var todos = TC.todos = todoStorage.get(); 22 | 23 | TC.ESCAPE_KEY = 27; 24 | TC.editedTodo = {}; 25 | 26 | function resetTodo() { 27 | TC.newTodo = {title: '', completed: false}; 28 | } 29 | 30 | resetTodo(); 31 | 32 | if ($location.path() === '') { 33 | $location.path('/'); 34 | } 35 | 36 | TC.location = $location; 37 | 38 | $scope.$watch('TC.location.path()', function (path) { 39 | TC.statusFilter = { '/active': {completed: false}, '/completed': {completed: true} }[path]; 40 | }); 41 | 42 | // 3rd argument `true` for deep object watching 43 | $scope.$watch('TC.todos', function () { 44 | TC.remainingCount = todos.filter(function (todo) { return !todo.completed; }).length; 45 | TC.allChecked = (TC.remainingCount === 0); 46 | 47 | // Save any changes to localStorage 48 | todoStorage.put(todos); 49 | }, true); 50 | 51 | TC.addTodo = function () { 52 | var newTitle = TC.newTodo.title = TC.newTodo.title.trim(); 53 | if (newTitle.length === 0) { 54 | return; 55 | } 56 | 57 | todos.push(TC.newTodo); 58 | resetTodo(); 59 | }; 60 | 61 | TC.editTodo = function (todo) { 62 | TC.editedTodo = todo; 63 | 64 | // Clone the original todo to restore it on demand. 65 | TC.originalTodo = angular.copy(todo); 66 | }; 67 | 68 | TC.doneEditing = function (todo, index) { 69 | TC.editedTodo = {}; 70 | todo.title = todo.title.trim(); 71 | 72 | if (!todo.title) { 73 | TC.removeTodo(index); 74 | } 75 | }; 76 | 77 | TC.revertEditing = function (index) { 78 | TC.editedTodo = {}; 79 | todos[index] = TC.originalTodo; 80 | }; 81 | 82 | TC.removeTodo = function (index) { 83 | todos.splice(index, 1); 84 | }; 85 | 86 | TC.clearCompletedTodos = function () { 87 | TC.todos = todos = todos.filter(function (val) { 88 | return !val.completed; 89 | }); 90 | }; 91 | 92 | TC.markAll = function (completed) { 93 | todos.forEach(function (todo) { 94 | todo.completed = completed; 95 | }); 96 | }; 97 | }); 98 | })(); 99 | //jscs:enable 100 | -------------------------------------------------------------------------------- /todomvc/angularjs-perf/js/directives/todoFocus.js: -------------------------------------------------------------------------------- 1 | /* jshint undef: true, unused: true */ 2 | /*global angular */ 3 | (function () { 4 | 'use strict'; 5 | 6 | angular.module('todoFocus', []) 7 | 8 | /** 9 | * Directive that places focus on the element it is applied to when the expression it binds to evaluates to true 10 | */ 11 | .directive('todoFocus', function ($timeout) { 12 | return function (scope, elem, attrs) { 13 | scope.$watch(attrs.todoFocus, function (newVal) { 14 | if (newVal) { 15 | $timeout(function () { 16 | elem[0].focus(); 17 | }, 0, false); 18 | } 19 | }); 20 | }; 21 | }); 22 | })(); 23 | -------------------------------------------------------------------------------- /todomvc/angularjs-perf/js/services/todoStorage.js: -------------------------------------------------------------------------------- 1 | /* jshint undef: true, unused: true */ 2 | /*global angular */ 3 | (function () { 4 | 'use strict'; 5 | 6 | angular.module('todoStorage', []) 7 | 8 | /** 9 | * Services that persists and retrieves TODOs from localStorage 10 | */ 11 | .factory('todoStorage', function () { 12 | var STORAGE_ID = 'todos-angularjs-perf'; 13 | 14 | return { 15 | get: function () { 16 | return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]'); 17 | }, 18 | 19 | put: function (todos) { 20 | localStorage.setItem(STORAGE_ID, JSON.stringify(todos)); 21 | } 22 | }; 23 | }); 24 | })(); 25 | -------------------------------------------------------------------------------- /todomvc/angularjs-perf/node_modules/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /todomvc/angularjs-perf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "angular": "1.5.3", 5 | "todomvc-common": "^1.0.1", 6 | "todomvc-app-css": "^1.0.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /todomvc/angularjs-perf/readme.md: -------------------------------------------------------------------------------- 1 | # AngularJS (Performance Optimized) TodoMVC Example 2 | 3 | > HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop. 4 | 5 | > _[AngularJS - angularjs.org](http://angularjs.org)_ 6 | 7 | 8 | ## Learning AngularJS 9 | The [AngularJS website](http://angularjs.org) is a great resource for getting started. 10 | 11 | Here are some links you may find helpful: 12 | 13 | * [Tutorial](http://docs.angularjs.org/tutorial) 14 | * [API Reference](http://docs.angularjs.org/api) 15 | * [Developer Guide](http://docs.angularjs.org/guide) 16 | * [Applications built with AngularJS](https://www.madewithangular.com/) 17 | * [Blog](http://blog.angularjs.org) 18 | * [FAQ](http://docs.angularjs.org/misc/faq) 19 | * [AngularJS Meetups](http://www.youtube.com/angularjs) 20 | 21 | Articles and guides from the community: 22 | 23 | * [Code School AngularJS course](https://www.codeschool.com/courses/shaping-up-with-angular-js) 24 | * [5 Awesome AngularJS Features](http://net.tutsplus.com/tutorials/javascript-ajax/5-awesome-angularjs-features) 25 | * [Using Yeoman with AngularJS](http://briantford.com/blog/angular-yeoman.html) 26 | * [me&ngular - an introduction to MVW](http://stephenplusplus.github.io/meangular) 27 | 28 | Get help from other AngularJS users: 29 | 30 | * [Walkthroughs and Tutorials on YouTube](http://www.youtube.com/playlist?list=PL1w1q3fL4pmgqpzb-XhG7Clgi67d_OHXz) 31 | * [Google Groups mailing list](https://groups.google.com/forum/?fromgroups#!forum/angular) 32 | * [angularjs on Stack Overflow](http://stackoverflow.com/questions/tagged/angularjs) 33 | * [AngularJS on Twitter](https://twitter.com/angularjs) 34 | * [AngularjS on Google +](https://plus.google.com/+AngularJS/posts) 35 | 36 | _If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ 37 | 38 | 39 | ## Implementation 40 | The normal AngularJS TodoMVC implementation performs deep watching of the todos array object. This means that it keeps an in-memory copy of the complete array that is used for dirty checking in order to detect model mutations. For smaller applications such as TodoMVC, this is completely fine as one trades off a little memory and performance for the sake of simplicity. 41 | 42 | In larger more complex applications however, where one might be working with 100s or 1000s of large objects one definitely should avoid using this approach. This implementation of the AngularJS app demonstrates the correct way to approach this problem when working in larger apps. 43 | -------------------------------------------------------------------------------- /todomvc/backbone/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-backbone", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "backbone": "~1.1.0", 6 | "underscore": "~1.5.0", 7 | "jquery": "~2.0.0", 8 | "todomvc-common": "~0.1.4", 9 | "backbone.localStorage": "~1.1.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /todomvc/backbone/bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rabbotio/todomvc-perf/f21ada979b180f5ea67dd1c78dfee3ef91f08bdc/todomvc/backbone/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /todomvc/backbone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Backbone.js • TodoMVC 7 | 8 | 9 | 10 |
11 | 15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 | 27 | 35 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /todomvc/backbone/js/app.js: -------------------------------------------------------------------------------- 1 | /*global $ */ 2 | /*jshint unused:false */ 3 | var app = app || {}; 4 | var ENTER_KEY = 13; 5 | var ESC_KEY = 27; 6 | 7 | $(function () { 8 | 'use strict'; 9 | 10 | // kick things off by creating the `App` 11 | window.appView = new app.AppView(); 12 | }); 13 | -------------------------------------------------------------------------------- /todomvc/backbone/js/collections/todos.js: -------------------------------------------------------------------------------- 1 | /*global Backbone */ 2 | var app = app || {}; 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | // Todo Collection 8 | // --------------- 9 | 10 | // The collection of todos is backed by *localStorage* instead of a remote 11 | // server. 12 | var Todos = Backbone.Collection.extend({ 13 | // Reference to this collection's model. 14 | model: app.Todo, 15 | 16 | // Save all of the todo items under the `"todos"` namespace. 17 | localStorage: new Backbone.LocalStorage('todos-backbone'), 18 | 19 | // Filter down the list of all todo items that are finished. 20 | completed: function () { 21 | return this.filter(function (todo) { 22 | return todo.get('completed'); 23 | }); 24 | }, 25 | 26 | // Filter down the list to only todo items that are still not finished. 27 | remaining: function () { 28 | return this.without.apply(this, this.completed()); 29 | }, 30 | 31 | // We keep the Todos in sequential order, despite being saved by unordered 32 | // GUID in the database. This generates the next order number for new items. 33 | nextOrder: function () { 34 | if (!this.length) { 35 | return 1; 36 | } 37 | return this.last().get('order') + 1; 38 | }, 39 | 40 | // Todos are sorted by their original insertion order. 41 | comparator: function (todo) { 42 | return todo.get('order'); 43 | } 44 | }); 45 | 46 | // Create our global collection of **Todos**. 47 | app.todos = new Todos(); 48 | })(); 49 | -------------------------------------------------------------------------------- /todomvc/backbone/js/models/todo.js: -------------------------------------------------------------------------------- 1 | /*global Backbone */ 2 | var app = app || {}; 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | // Todo Model 8 | // ---------- 9 | 10 | // Our basic **Todo** model has `title`, `order`, and `completed` attributes. 11 | app.Todo = Backbone.Model.extend({ 12 | // Default attributes for the todo 13 | // and ensure that each todo created has `title` and `completed` keys. 14 | defaults: { 15 | title: '', 16 | completed: false 17 | }, 18 | 19 | // Toggle the `completed` state of this todo item. 20 | toggle: function () { 21 | this.save({ 22 | completed: !this.get('completed') 23 | }); 24 | } 25 | }); 26 | })(); 27 | -------------------------------------------------------------------------------- /todomvc/backbone/js/routers/router.js: -------------------------------------------------------------------------------- 1 | /*global Backbone */ 2 | var app = app || {}; 3 | 4 | (function () { 5 | 'use strict'; 6 | 7 | // Todo Router 8 | // ---------- 9 | var TodoRouter = Backbone.Router.extend({ 10 | routes: { 11 | '*filter': 'setFilter' 12 | }, 13 | 14 | setFilter: function (param) { 15 | // Set the current filter to be used 16 | app.TodoFilter = param || ''; 17 | 18 | // Trigger a collection filter event, causing hiding/unhiding 19 | // of Todo view items 20 | app.todos.trigger('filter'); 21 | } 22 | }); 23 | 24 | app.TodoRouter = new TodoRouter(); 25 | Backbone.history.start(); 26 | })(); 27 | -------------------------------------------------------------------------------- /todomvc/backbone/js/views/app-view.js: -------------------------------------------------------------------------------- 1 | /*global Backbone, jQuery, _, ENTER_KEY */ 2 | var app = app || {}; 3 | 4 | (function ($) { 5 | 'use strict'; 6 | 7 | // The Application 8 | // --------------- 9 | 10 | // Our overall **AppView** is the top-level piece of UI. 11 | app.AppView = Backbone.View.extend({ 12 | 13 | // Instead of generating a new element, bind to the existing skeleton of 14 | // the App already present in the HTML. 15 | el: '#todoapp', 16 | 17 | // Our template for the line of statistics at the bottom of the app. 18 | statsTemplate: _.template($('#stats-template').html()), 19 | 20 | // Delegated events for creating new items, and clearing completed ones. 21 | events: { 22 | 'keypress #new-todo': 'createOnEnter', 23 | 'click #clear-completed': 'clearCompleted', 24 | 'click #toggle-all': 'toggleAllComplete' 25 | }, 26 | 27 | // At initialization we bind to the relevant events on the `Todos` 28 | // collection, when items are added or changed. Kick things off by 29 | // loading any preexisting todos that might be saved in *localStorage*. 30 | initialize: function () { 31 | this.allCheckbox = this.$('#toggle-all')[0]; 32 | this.$input = this.$('#new-todo'); 33 | this.$footer = this.$('#footer'); 34 | this.$main = this.$('#main'); 35 | this.$list = $('#todo-list'); 36 | 37 | this.listenTo(app.todos, 'add', this.addOne); 38 | this.listenTo(app.todos, 'reset', this.addAll); 39 | this.listenTo(app.todos, 'change:completed', this.filterOne); 40 | this.listenTo(app.todos, 'filter', this.filterAll); 41 | this.listenTo(app.todos, 'all', this.render); 42 | 43 | // Suppresses 'add' events with {reset: true} and prevents the app view 44 | // from being re-rendered for every model. Only renders when the 'reset' 45 | // event is triggered at the end of the fetch. 46 | app.todos.fetch({reset: true}); 47 | }, 48 | 49 | // Re-rendering the App just means refreshing the statistics -- the rest 50 | // of the app doesn't change. 51 | render: function () { 52 | var completed = app.todos.completed().length; 53 | var remaining = app.todos.remaining().length; 54 | 55 | if (app.todos.length) { 56 | this.$main.show(); 57 | this.$footer.show(); 58 | 59 | this.$footer.html(this.statsTemplate({ 60 | completed: completed, 61 | remaining: remaining 62 | })); 63 | 64 | this.$('#filters li a') 65 | .removeClass('selected') 66 | .filter('[href="#/' + (app.TodoFilter || '') + '"]') 67 | .addClass('selected'); 68 | } else { 69 | this.$main.hide(); 70 | this.$footer.hide(); 71 | } 72 | 73 | this.allCheckbox.checked = !remaining; 74 | }, 75 | 76 | // Add a single todo item to the list by creating a view for it, and 77 | // appending its element to the `