├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── bundle.js └── bundle.js.map ├── index.html ├── package.json ├── src ├── Controller.ts ├── Store.ts ├── Todo.ts ├── View.ts ├── app.ts ├── helpers.ts └── templates.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "6" 5 | - "5" 6 | - "4" 7 | 8 | install: 9 | - npm install 10 | script: 11 | - npm run compile 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 HyunSeob 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typescript-TodoMVC 2 | 3 | [![Travis](https://img.shields.io/travis/HyunSeob/typescript-TodoMVC.svg)](https://travis-ci.org/HyunSeob/typescript-TodoMVC) ![Heroku](https://heroku-badge.herokuapp.com/?app=typescript-todomvc&style=flat&svg=1) 4 | 5 | This repository is TodoMVC implemented with TypeScript. Not using other frameworks. 6 | 7 | [Demo](https://typescript-todomvc.herokuapp.com/) 8 | 9 | ## Dependencies 10 | 11 | - [TypeScript](http://www.typescriptlang.org/) (v1.8) 12 | - [SystemJS](https://github.com/systemjs/systemjs) 13 | - [http-server](https://github.com/indexzero/http-server) (server only) 14 | 15 | 16 | ## Disclaimer 17 | 18 | This project is influenced by the [TodoMVC - Vanilla ES6](https://github.com/tastejs/todomvc/tree/gh-pages/examples/vanilla-es6) project. 19 | -------------------------------------------------------------------------------- /dist/bundle.js: -------------------------------------------------------------------------------- 1 | System.register("helpers", [], function(exports_1, context_1) { 2 | "use strict"; 3 | var __moduleName = context_1 && context_1.id; 4 | function token() { 5 | return Math.floor((Math.random() + 1) * 0x10000) 6 | .toString(16) 7 | .substring(1); 8 | } 9 | function guid() { 10 | return [ 11 | token() + token(), 12 | token(), 13 | token(), 14 | token(), 15 | token() + token() 16 | ].join('-'); 17 | } 18 | exports_1("guid", guid); 19 | return { 20 | setters:[], 21 | execute: function() { 22 | } 23 | } 24 | }); 25 | System.register("Todo", ["helpers"], function(exports_2, context_2) { 26 | "use strict"; 27 | var __moduleName = context_2 && context_2.id; 28 | var helpers_1; 29 | var Todo; 30 | return { 31 | setters:[ 32 | function (helpers_1_1) { 33 | helpers_1 = helpers_1_1; 34 | }], 35 | execute: function() { 36 | Todo = (function () { 37 | function Todo(_todo) { 38 | if (typeof _todo === 'string') { 39 | this._id = helpers_1.guid(); 40 | this._content = _todo; 41 | this._done = false; 42 | } 43 | else { 44 | this._id = _todo._id; 45 | this._content = _todo._content; 46 | this._done = _todo._done; 47 | } 48 | } 49 | Object.defineProperty(Todo.prototype, "id", { 50 | get: function () { return this._id; }, 51 | enumerable: true, 52 | configurable: true 53 | }); 54 | Object.defineProperty(Todo.prototype, "content", { 55 | get: function () { return this._content; }, 56 | set: function (_content) { this._content = _content; }, 57 | enumerable: true, 58 | configurable: true 59 | }); 60 | Object.defineProperty(Todo.prototype, "done", { 61 | get: function () { return this._done; }, 62 | enumerable: true, 63 | configurable: true 64 | }); 65 | Todo.prototype.equal = function (_todo) { return this._id === _todo.id; }; 66 | Todo.prototype.toggle = function () { return this._done = !this._done; }; 67 | return Todo; 68 | }()); 69 | exports_2("default", Todo); 70 | } 71 | } 72 | }); 73 | System.register("Store", ["Todo"], function(exports_3, context_3) { 74 | "use strict"; 75 | var __moduleName = context_3 && context_3.id; 76 | var Todo_1; 77 | var Store; 78 | return { 79 | setters:[ 80 | function (Todo_1_1) { 81 | Todo_1 = Todo_1_1; 82 | }], 83 | execute: function() { 84 | Store = (function () { 85 | function Store() { 86 | this._localStorage = window.localStorage; 87 | this._subscribers = []; 88 | } 89 | Store.prototype._getStorage = function () { 90 | return this._todos = this._todos || ((JSON.parse(this._localStorage.getItem('TodoList'))) || []).map(function (item) { return new Todo_1.default(item); }); 91 | }; 92 | Store.prototype._setStorage = function (todos) { 93 | this._localStorage.setItem('TodoList', JSON.stringify(todos)); 94 | this._publish(); 95 | }; 96 | Store.prototype.initialize = function () { 97 | this._getStorage(); 98 | this._publish(); 99 | return this; 100 | }; 101 | Store.prototype.insert = function (todo) { 102 | this._setStorage(this._todos = this._getStorage().concat([todo])); 103 | }; 104 | Store.prototype.find = function (query) { 105 | return this._getStorage().filter(function (todo) { 106 | for (var key in query) { 107 | if (todo[key] !== query[key]) { 108 | return false; 109 | } 110 | } 111 | return true; 112 | }); 113 | }; 114 | Store.prototype.update = function (patch) { 115 | this._setStorage(this._getStorage().map(function (target) { 116 | if (target.equal(patch)) { 117 | target = patch; 118 | } 119 | return target; 120 | })); 121 | }; 122 | Store.prototype.remove = function (query) { 123 | this._setStorage(this._todos = this._getStorage() 124 | .filter(function (todo) { 125 | for (var key in query) { 126 | if (todo[key] !== query[key]) { 127 | return true; 128 | } 129 | } 130 | return false; 131 | })); 132 | }; 133 | Store.prototype.count = function (query) { 134 | return this._getStorage().filter(function (todo) { 135 | for (var key in query) { 136 | if (todo[key] !== query[key]) { 137 | return false; 138 | } 139 | } 140 | return true; 141 | }).length; 142 | }; 143 | Store.prototype.subscribe = function (subscriber) { 144 | this._subscribers.push(subscriber); 145 | }; 146 | Store.prototype._publish = function () { 147 | var _this = this; 148 | this._subscribers.forEach(function (s) { return s(_this._getStorage()); }); 149 | }; 150 | return Store; 151 | }()); 152 | exports_3("default", Store); 153 | } 154 | } 155 | }); 156 | System.register("templates", [], function(exports_4, context_4) { 157 | "use strict"; 158 | var __moduleName = context_4 && context_4.id; 159 | function todoTpl(todo) { 160 | return [ 161 | ("
  • "), 162 | (""), 163 | (""), 164 | "", 165 | "
  • " 166 | ].join(''); 167 | } 168 | exports_4("todoTpl", todoTpl); 169 | function todoListTpl(todos) { 170 | return todos.map(function (t) { return todoTpl(t); }).join(''); 171 | } 172 | exports_4("todoListTpl", todoListTpl); 173 | function statTpl(leftTodos) { 174 | return leftTodos + " item" + (leftTodos === 1 ? '' : 's') + " left."; 175 | } 176 | exports_4("statTpl", statTpl); 177 | return { 178 | setters:[], 179 | execute: function() { 180 | } 181 | } 182 | }); 183 | System.register("View", ["templates"], function(exports_5, context_5) { 184 | "use strict"; 185 | var __moduleName = context_5 && context_5.id; 186 | var templates_1; 187 | var ENTER_KEY, ESCAPE_KEY, View; 188 | return { 189 | setters:[ 190 | function (templates_1_1) { 191 | templates_1 = templates_1_1; 192 | }], 193 | execute: function() { 194 | ENTER_KEY = 13; 195 | ESCAPE_KEY = 27; 196 | View = (function () { 197 | function View() { 198 | this.$todoList = document.querySelector('#todo-list'); 199 | this.$todoInput = document.querySelector('#todo-input'); 200 | this.$todoStat = document.querySelector('#todo-stat'); 201 | } 202 | View.prototype.onSubmit = function (handler) { 203 | this.$todoInput.addEventListener('keypress', function (event) { 204 | if (event.keyCode === ENTER_KEY) { 205 | var target = event.target; 206 | handler(target.value); 207 | target.value = ''; 208 | } 209 | }); 210 | }; 211 | View.prototype.onClickCheckbox = function (handler) { 212 | this.$todoList.addEventListener('click', function (event) { 213 | var target = event.target; 214 | if (target.tagName === 'INPUT' && target.type === 'checkbox') { 215 | handler(target.parentElement.dataset['id']); 216 | } 217 | }); 218 | }; 219 | View.prototype.onClickButton = function (handler) { 220 | this.$todoList.addEventListener('click', function (event) { 221 | var target = event.target; 222 | if (target.tagName === 'BUTTON') { 223 | handler(target.parentElement.dataset['id']); 224 | } 225 | }); 226 | }; 227 | View.prototype.render = function (todos, lefts) { 228 | this.$todoList.innerHTML = templates_1.todoListTpl(todos); 229 | this.$todoStat.innerHTML = templates_1.statTpl(lefts); 230 | }; 231 | return View; 232 | }()); 233 | exports_5("default", View); 234 | } 235 | } 236 | }); 237 | System.register("Controller", ["Todo", "Store", "View"], function(exports_6, context_6) { 238 | "use strict"; 239 | var __moduleName = context_6 && context_6.id; 240 | var Todo_2, Store_1, View_1; 241 | var Controller; 242 | return { 243 | setters:[ 244 | function (Todo_2_1) { 245 | Todo_2 = Todo_2_1; 246 | }, 247 | function (Store_1_1) { 248 | Store_1 = Store_1_1; 249 | }, 250 | function (View_1_1) { 251 | View_1 = View_1_1; 252 | }], 253 | execute: function() { 254 | Controller = (function () { 255 | function Controller() { 256 | var _this = this; 257 | this._view = new View_1.default(); 258 | this._store = new Store_1.default(); 259 | this._view.onSubmit(function (value) { 260 | var todo = new Todo_2.default(value); 261 | _this._store.insert(todo); 262 | }); 263 | this._view.onClickCheckbox(function (targetId) { 264 | var targetTodo = _this._store.find({ id: targetId })[0]; 265 | targetTodo.toggle(); 266 | _this._store.update(targetTodo); 267 | }); 268 | this._view.onClickButton(function (targetId) { 269 | _this._store.remove({ id: targetId }); 270 | }); 271 | this._store.subscribe(function (todos) { 272 | _this._view.render(todos, _this._store.count({ done: false })); 273 | }); 274 | this._store.initialize(); 275 | } 276 | return Controller; 277 | }()); 278 | exports_6("default", Controller); 279 | } 280 | } 281 | }); 282 | System.register("app", ["Controller"], function(exports_7, context_7) { 283 | "use strict"; 284 | var __moduleName = context_7 && context_7.id; 285 | var Controller_1; 286 | var controller; 287 | return { 288 | setters:[ 289 | function (Controller_1_1) { 290 | Controller_1 = Controller_1_1; 291 | }], 292 | execute: function() { 293 | controller = new Controller_1.default(); 294 | } 295 | } 296 | }); 297 | //# sourceMappingURL=bundle.js.map -------------------------------------------------------------------------------- /dist/bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/helpers.ts","../src/Todo.ts","../src/Store.ts","../src/templates.ts","../src/View.ts","../src/Controller.ts","../src/app.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TypeScript TodoMVC 6 | 7 | 8 |
    9 |
    10 |

    Todo list

    11 | 12 |
    13 | 18 | 21 |
    22 | 23 | 24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-todo", 3 | "version": "0.1.0", 4 | "description": "A Todo application implemented by typescript.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "./node_modules/.bin/http-server .", 8 | "compile": "rm dist/bundle.js.map; rm dist/bundle.js; ./node_modules/.bin/tsc --project ./tsconfig.json", 9 | "watch": "rm dist/bundle.js.map; rm dist/bundle.js; ./node_modules/.bin/tsc --watch --project ./tsconfig.json" 10 | }, 11 | "keywords": [ 12 | "Typescript", 13 | "Todo", 14 | "TodoApp" 15 | ], 16 | "author": "HyunSeob (http://hyunseob.github.io)", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "typescript": "^1.8.10" 20 | }, 21 | "dependencies": { 22 | "http-server": "^0.9.0", 23 | "systemjs": "^0.19.38" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Controller.ts: -------------------------------------------------------------------------------- 1 | import Todo from './Todo'; 2 | import Store from './Store'; 3 | import View from './View'; 4 | 5 | export default class Controller { 6 | 7 | private _view : View; 8 | private _store: Store; 9 | 10 | constructor() { 11 | this._view = new View(); 12 | this._store = new Store(); 13 | 14 | this._view.onSubmit((value: string) => { 15 | const todo: Todo = new Todo(value); 16 | this._store.insert(todo); 17 | }); 18 | 19 | this._view.onClickCheckbox((targetId: string) => { 20 | const targetTodo: Todo = this._store.find({ id: targetId })[0]; 21 | targetTodo.toggle(); 22 | this._store.update(targetTodo); 23 | }); 24 | 25 | this._view.onClickButton((targetId: string) => { 26 | this._store.remove({ id: targetId }); 27 | }); 28 | 29 | this._store.subscribe((todos: Array) => { 30 | this._view.render(todos, this._store.count({ done: false })); 31 | }); 32 | 33 | this._store.initialize(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Store.ts: -------------------------------------------------------------------------------- 1 | import Todo from './Todo'; 2 | 3 | export default class Store { 4 | 5 | private _localStorage: Storage; 6 | private _todos : Array; 7 | private _subscribers : Array; 8 | 9 | constructor() { 10 | this._localStorage = window.localStorage; 11 | this._subscribers = []; 12 | } 13 | 14 | private _getStorage(): Array { 15 | return this._todos = this._todos || ((JSON.parse( 16 | this._localStorage.getItem('TodoList') 17 | )) || []).map((item: Object) => new Todo(item)); 18 | } 19 | 20 | private _setStorage(todos: Array): void { 21 | this._localStorage.setItem( 22 | 'TodoList', 23 | JSON.stringify(todos) 24 | ); 25 | this._publish(); 26 | } 27 | 28 | initialize(): Store { 29 | this._getStorage(); 30 | this._publish(); 31 | return this; 32 | } 33 | 34 | insert(todo: Todo): void { 35 | this._setStorage( 36 | this._todos = this._getStorage().concat([ todo ]) 37 | ); 38 | } 39 | 40 | find(query: any): Array { 41 | return this._getStorage().filter((todo: Todo) => { 42 | for (let key in query) { 43 | if (todo[key] !== query[key]) { 44 | return false; 45 | } 46 | } 47 | return true; 48 | }); 49 | } 50 | 51 | update(patch: Todo): void { 52 | this._setStorage( 53 | this._getStorage().map((target: Todo) => { 54 | if (target.equal(patch)) { 55 | target = patch; 56 | } 57 | return target; 58 | }) 59 | ); 60 | } 61 | 62 | remove(query: any): void { 63 | this._setStorage( 64 | this._todos = this._getStorage() 65 | .filter((todo: Todo) => { 66 | for (let key in query) { 67 | if (todo[key] !== query[key]) { 68 | return true; 69 | } 70 | } 71 | return false; 72 | }) 73 | ); 74 | } 75 | 76 | count(query: any): Number { 77 | return this._getStorage().filter((todo: Todo) => { 78 | for (let key in query) { 79 | if (todo[key] !== query[key]) { 80 | return false; 81 | } 82 | } 83 | return true; 84 | }).length; 85 | } 86 | 87 | subscribe(subscriber: Function): void { 88 | this._subscribers.push(subscriber); 89 | } 90 | 91 | private _publish(): void { 92 | this._subscribers.forEach(s => s(this._getStorage())); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Todo.ts: -------------------------------------------------------------------------------- 1 | import { guid } from './helpers'; 2 | 3 | export default class Todo { 4 | 5 | [index: string]: any; /** Indexible */ 6 | 7 | private _id : string; 8 | private _content: string; 9 | private _done : boolean; 10 | 11 | constructor(_todo: string | any) { 12 | if (typeof _todo === 'string') { 13 | this._id = guid(); 14 | this._content = _todo; 15 | this._done = false; 16 | } else { 17 | this._id = _todo._id; 18 | this._content = _todo._content; 19 | this._done = _todo._done; 20 | } 21 | } 22 | 23 | get id (): string { return this._id; } 24 | get content(): string { return this._content; } 25 | get done (): boolean { return this._done; } 26 | 27 | set content(_content: string) { this._content = _content; } 28 | 29 | equal(_todo: Todo): boolean { return this._id === _todo.id; } 30 | 31 | toggle(): boolean { return this._done = !this._done; } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/View.ts: -------------------------------------------------------------------------------- 1 | import Todo from './Todo'; 2 | import { todoListTpl, statTpl } from './templates'; 3 | 4 | const ENTER_KEY = 13; 5 | const ESCAPE_KEY = 27; 6 | 7 | export default class View { 8 | 9 | private $todoList : Element; 10 | private $todoInput : Element; 11 | private $todoStat : Element; 12 | private _submitHandler: Function; 13 | 14 | constructor() { 15 | this.$todoList = document.querySelector('#todo-list'); 16 | this.$todoInput = document.querySelector('#todo-input'); 17 | this.$todoStat = document.querySelector('#todo-stat'); 18 | } 19 | 20 | onSubmit(handler: Function): void { 21 | this.$todoInput.addEventListener('keypress', (event: KeyboardEvent) => { 22 | if (event.keyCode === ENTER_KEY) { 23 | const target: HTMLInputElement = event.target; 24 | handler(target.value); 25 | target.value = ''; 26 | } 27 | }); 28 | } 29 | 30 | onClickCheckbox(handler: Function): void { 31 | this.$todoList.addEventListener('click', (event: Event) => { 32 | const target: HTMLInputElement = event.target; 33 | if (target.tagName === 'INPUT' && target.type === 'checkbox') { 34 | handler(target.parentElement.dataset['id']); 35 | } 36 | }); 37 | } 38 | 39 | onClickButton(handler: Function): void { 40 | this.$todoList.addEventListener('click', (event: Event) => { 41 | const target: HTMLElement = event.target; 42 | if (target.tagName === 'BUTTON') { 43 | handler(target.parentElement.dataset['id']); 44 | } 45 | }); 46 | } 47 | 48 | render(todos: Array, lefts: Number): void { 49 | this.$todoList.innerHTML = todoListTpl(todos); 50 | this.$todoStat.innerHTML = statTpl(lefts); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import Controller from './Controller'; 2 | 3 | const controller: Controller = new Controller(); 4 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | function token(): string { 2 | return Math.floor((Math.random() + 1) * 0x10000) 3 | .toString(16) 4 | .substring(1); 5 | } 6 | 7 | export function guid(): string { 8 | /** 9 | * Generate a GUID. 10 | * 11 | * @example guid(); 12 | */ 13 | return [ 14 | token() + token(), 15 | token(), 16 | token(), 17 | token(), 18 | token() + token() 19 | ].join('-'); 20 | } 21 | -------------------------------------------------------------------------------- /src/templates.ts: -------------------------------------------------------------------------------- 1 | import Todo from './Todo'; 2 | 3 | export function todoTpl(todo: Todo): string { 4 | return [ 5 | `
  • `, 6 | ``, 7 | ``, 8 | ``, 9 | `
  • ` 10 | ].join(''); 11 | } 12 | 13 | export function todoListTpl(todos: Array): string { 14 | return todos.map((t: Todo) => todoTpl(t)).join(''); 15 | } 16 | 17 | export function statTpl(leftTodos: Number): string { 18 | return `${leftTodos} item${leftTodos === 1 ? '' : 's'} left.`; 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "system", 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "target": "ES5", 9 | "outFile": "./dist/bundle.js", 10 | "pretty": true 11 | }, 12 | "exclude": [ 13 | "node_modules" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------