├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── bower_components └── todomvc-common │ ├── base.css │ ├── base.js │ └── bg.png ├── css └── app.css ├── index.html └── js ├── app.js ├── controllers └── todo.js ├── models └── todo.js └── views ├── footer-view.js ├── main-view.js └── single-view.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/* 2 | sample.html -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jean-Philippe Monette 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mithril TodoMVC Example 2 | 3 | > Mithril is a client-side Javascript MVC framework, i.e. it's a tool to make application code divided into a data layer (called "Model"), a UI layer (called View), and a glue layer (called Controller) 4 | 5 | > _[Mithril](http://lhorie.github.io/mithril/)_ 6 | 7 | 8 | ## Learning Mithril 9 | 10 | The [Mithril website](http://lhorie.github.io/mithril/) is a great resource for getting started. 11 | 12 | Here are some links you may find helpful: 13 | 14 | * [Getting Started](http://lhorie.github.io/mithril/getting-started.html) 15 | * [API Reference](http://lhorie.github.io/mithril/mithril.html) 16 | * [Mithril on Github](https://github.com/lhorie/mithril.js) 17 | 18 | _If you have other helpful links to share, or find any of the links above no longer work, please [let me know](https://github.com/jpmonette/todomvc-mithril/issues)._ 19 | 20 | ## Running 21 | 22 | 1. Clone the repo 23 | 2. Execute `bower install` in the repo (make sure yo install `npm`) 24 | 3. Open `index.html`! 25 | 26 | ## TODO 27 | 28 | * Proper routing support (fixed, waiting for latest Mithril version (v0.1.1)) 29 | * Select all 30 | * Fix items left behaviour 31 | 32 | ## Credit 33 | 34 | This TodoMVC application was created by [Jean-Philippe Monette](http://blogue.jpmonette.net/) 35 | 36 | ## License 37 | 38 | Copyright (C) 2013, Jean-Philippe Monette 39 | 40 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 41 | 42 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 45 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-mithril", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "mithril": "git://github.com/lhorie/mithril.js.git", 6 | "todomvc-common": "~0.1.4" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /bower_components/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | -ms-appearance: none; 18 | -o-appearance: none; 19 | appearance: none; 20 | } 21 | 22 | body { 23 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 24 | line-height: 1.4em; 25 | background: #eaeaea url('bg.png'); 26 | color: #4d4d4d; 27 | width: 550px; 28 | margin: 0 auto; 29 | -webkit-font-smoothing: antialiased; 30 | -moz-font-smoothing: antialiased; 31 | -ms-font-smoothing: antialiased; 32 | -o-font-smoothing: antialiased; 33 | font-smoothing: antialiased; 34 | } 35 | 36 | button, 37 | input[type="checkbox"] { 38 | outline: none; 39 | } 40 | 41 | #todoapp { 42 | background: #fff; 43 | background: rgba(255, 255, 255, 0.9); 44 | margin: 130px 0 40px 0; 45 | border: 1px solid #ccc; 46 | position: relative; 47 | border-top-left-radius: 2px; 48 | border-top-right-radius: 2px; 49 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 50 | 0 25px 50px 0 rgba(0, 0, 0, 0.15); 51 | } 52 | 53 | #todoapp:before { 54 | content: ''; 55 | border-left: 1px solid #f5d6d6; 56 | border-right: 1px solid #f5d6d6; 57 | width: 2px; 58 | position: absolute; 59 | top: 0; 60 | left: 40px; 61 | height: 100%; 62 | } 63 | 64 | #todoapp input::-webkit-input-placeholder { 65 | font-style: italic; 66 | } 67 | 68 | #todoapp input::-moz-placeholder { 69 | font-style: italic; 70 | color: #a9a9a9; 71 | } 72 | 73 | #todoapp h1 { 74 | position: absolute; 75 | top: -120px; 76 | width: 100%; 77 | font-size: 70px; 78 | font-weight: bold; 79 | text-align: center; 80 | color: #b3b3b3; 81 | color: rgba(255, 255, 255, 0.3); 82 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2); 83 | -webkit-text-rendering: optimizeLegibility; 84 | -moz-text-rendering: optimizeLegibility; 85 | -ms-text-rendering: optimizeLegibility; 86 | -o-text-rendering: optimizeLegibility; 87 | text-rendering: optimizeLegibility; 88 | } 89 | 90 | #header { 91 | padding-top: 15px; 92 | border-radius: inherit; 93 | } 94 | 95 | #header:before { 96 | content: ''; 97 | position: absolute; 98 | top: 0; 99 | right: 0; 100 | left: 0; 101 | height: 15px; 102 | z-index: 2; 103 | border-bottom: 1px solid #6c615c; 104 | background: #8d7d77; 105 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); 106 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 107 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 108 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); 109 | border-top-left-radius: 1px; 110 | border-top-right-radius: 1px; 111 | } 112 | 113 | #new-todo, 114 | .edit { 115 | position: relative; 116 | margin: 0; 117 | width: 100%; 118 | font-size: 24px; 119 | font-family: inherit; 120 | line-height: 1.4em; 121 | border: 0; 122 | outline: none; 123 | color: inherit; 124 | padding: 6px; 125 | border: 1px solid #999; 126 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 127 | -moz-box-sizing: border-box; 128 | -ms-box-sizing: border-box; 129 | -o-box-sizing: border-box; 130 | box-sizing: border-box; 131 | -webkit-font-smoothing: antialiased; 132 | -moz-font-smoothing: antialiased; 133 | -ms-font-smoothing: antialiased; 134 | -o-font-smoothing: antialiased; 135 | font-smoothing: antialiased; 136 | } 137 | 138 | #new-todo { 139 | padding: 16px 16px 16px 60px; 140 | border: none; 141 | background: rgba(0, 0, 0, 0.02); 142 | z-index: 2; 143 | box-shadow: none; 144 | } 145 | 146 | #main { 147 | position: relative; 148 | z-index: 2; 149 | border-top: 1px dotted #adadad; 150 | } 151 | 152 | label[for='toggle-all'] { 153 | display: none; 154 | } 155 | 156 | #toggle-all { 157 | position: absolute; 158 | top: -42px; 159 | left: -4px; 160 | width: 40px; 161 | text-align: center; 162 | /* Mobile Safari */ 163 | border: none; 164 | } 165 | 166 | #toggle-all:before { 167 | content: '»'; 168 | font-size: 28px; 169 | color: #d9d9d9; 170 | padding: 0 25px 7px; 171 | } 172 | 173 | #toggle-all:checked:before { 174 | color: #737373; 175 | } 176 | 177 | #todo-list { 178 | margin: 0; 179 | padding: 0; 180 | list-style: none; 181 | } 182 | 183 | #todo-list li { 184 | position: relative; 185 | font-size: 24px; 186 | border-bottom: 1px dotted #ccc; 187 | } 188 | 189 | #todo-list li:last-child { 190 | border-bottom: none; 191 | } 192 | 193 | #todo-list li.editing { 194 | border-bottom: none; 195 | padding: 0; 196 | } 197 | 198 | #todo-list li.editing .edit { 199 | display: block; 200 | width: 506px; 201 | padding: 13px 17px 12px 17px; 202 | margin: 0 0 0 43px; 203 | } 204 | 205 | #todo-list li.editing .view { 206 | display: none; 207 | } 208 | 209 | #todo-list li .toggle { 210 | text-align: center; 211 | width: 40px; 212 | /* auto, since non-WebKit browsers doesn't support input styling */ 213 | height: auto; 214 | position: absolute; 215 | top: 0; 216 | bottom: 0; 217 | margin: auto 0; 218 | /* Mobile Safari */ 219 | border: none; 220 | -webkit-appearance: none; 221 | -ms-appearance: none; 222 | -o-appearance: none; 223 | appearance: none; 224 | } 225 | 226 | #todo-list li .toggle:after { 227 | content: '✔'; 228 | /* 40 + a couple of pixels visual adjustment */ 229 | line-height: 43px; 230 | font-size: 20px; 231 | color: #d9d9d9; 232 | text-shadow: 0 -1px 0 #bfbfbf; 233 | } 234 | 235 | #todo-list li .toggle:checked:after { 236 | color: #85ada7; 237 | text-shadow: 0 1px 0 #669991; 238 | bottom: 1px; 239 | position: relative; 240 | } 241 | 242 | #todo-list li label { 243 | white-space: pre; 244 | word-break: break-word; 245 | padding: 15px 60px 15px 15px; 246 | margin-left: 45px; 247 | display: block; 248 | line-height: 1.2; 249 | -webkit-transition: color 0.4s; 250 | transition: color 0.4s; 251 | } 252 | 253 | #todo-list li.completed label { 254 | color: #a9a9a9; 255 | text-decoration: line-through; 256 | } 257 | 258 | #todo-list li .destroy { 259 | display: none; 260 | position: absolute; 261 | top: 0; 262 | right: 10px; 263 | bottom: 0; 264 | width: 40px; 265 | height: 40px; 266 | margin: auto 0; 267 | font-size: 22px; 268 | color: #a88a8a; 269 | -webkit-transition: all 0.2s; 270 | transition: all 0.2s; 271 | } 272 | 273 | #todo-list li .destroy:hover { 274 | text-shadow: 0 0 1px #000, 275 | 0 0 10px rgba(199, 107, 107, 0.8); 276 | -webkit-transform: scale(1.3); 277 | -ms-transform: scale(1.3); 278 | transform: scale(1.3); 279 | } 280 | 281 | #todo-list li .destroy:after { 282 | content: '✖'; 283 | } 284 | 285 | #todo-list li:hover .destroy { 286 | display: block; 287 | } 288 | 289 | #todo-list li .edit { 290 | display: none; 291 | } 292 | 293 | #todo-list li.editing:last-child { 294 | margin-bottom: -1px; 295 | } 296 | 297 | #footer { 298 | color: #777; 299 | padding: 0 15px; 300 | position: absolute; 301 | right: 0; 302 | bottom: -31px; 303 | left: 0; 304 | height: 20px; 305 | z-index: 1; 306 | text-align: center; 307 | } 308 | 309 | #footer:before { 310 | content: ''; 311 | position: absolute; 312 | right: 0; 313 | bottom: 31px; 314 | left: 0; 315 | height: 50px; 316 | z-index: -1; 317 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 318 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 319 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 320 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 321 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 322 | } 323 | 324 | #todo-count { 325 | float: left; 326 | text-align: left; 327 | } 328 | 329 | #filters { 330 | margin: 0; 331 | padding: 0; 332 | list-style: none; 333 | position: absolute; 334 | right: 0; 335 | left: 0; 336 | } 337 | 338 | #filters li { 339 | display: inline; 340 | } 341 | 342 | #filters li a { 343 | color: #83756f; 344 | margin: 2px; 345 | text-decoration: none; 346 | } 347 | 348 | #filters li a.selected { 349 | font-weight: bold; 350 | } 351 | 352 | #clear-completed { 353 | float: right; 354 | position: relative; 355 | line-height: 20px; 356 | text-decoration: none; 357 | background: rgba(0, 0, 0, 0.1); 358 | font-size: 11px; 359 | padding: 0 10px; 360 | border-radius: 3px; 361 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 362 | } 363 | 364 | #clear-completed:hover { 365 | background: rgba(0, 0, 0, 0.15); 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 367 | } 368 | 369 | #info { 370 | margin: 65px auto 0; 371 | color: #a6a6a6; 372 | font-size: 12px; 373 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 374 | text-align: center; 375 | } 376 | 377 | #info a { 378 | color: inherit; 379 | } 380 | 381 | /* 382 | Hack to remove background from Mobile Safari. 383 | Can't use it globally since it destroys checkboxes in Firefox and Opera 384 | */ 385 | 386 | @media screen and (-webkit-min-device-pixel-ratio:0) { 387 | #toggle-all, 388 | #todo-list li .toggle { 389 | background: none; 390 | } 391 | 392 | #todo-list li .toggle { 393 | height: 40px; 394 | } 395 | 396 | #toggle-all { 397 | top: -56px; 398 | left: -15px; 399 | width: 65px; 400 | height: 41px; 401 | -webkit-transform: rotate(90deg); 402 | -ms-transform: rotate(90deg); 403 | transform: rotate(90deg); 404 | -webkit-appearance: none; 405 | appearance: none; 406 | } 407 | } 408 | 409 | .hidden { 410 | display: none; 411 | } 412 | 413 | hr { 414 | margin: 20px 0; 415 | border: 0; 416 | border-top: 1px dashed #C5C5C5; 417 | border-bottom: 1px dashed #F7F7F7; 418 | } 419 | 420 | .learn a { 421 | font-weight: normal; 422 | text-decoration: none; 423 | color: #b83f45; 424 | } 425 | 426 | .learn a:hover { 427 | text-decoration: underline; 428 | color: #787e7e; 429 | } 430 | 431 | .learn h3, 432 | .learn h4, 433 | .learn h5 { 434 | margin: 10px 0; 435 | font-weight: 500; 436 | line-height: 1.2; 437 | color: #000; 438 | } 439 | 440 | .learn h3 { 441 | font-size: 24px; 442 | } 443 | 444 | .learn h4 { 445 | font-size: 18px; 446 | } 447 | 448 | .learn h5 { 449 | margin-bottom: 0; 450 | font-size: 14px; 451 | } 452 | 453 | .learn ul { 454 | padding: 0; 455 | margin: 0 0 30px 25px; 456 | } 457 | 458 | .learn li { 459 | line-height: 20px; 460 | } 461 | 462 | .learn p { 463 | font-size: 15px; 464 | font-weight: 300; 465 | line-height: 1.3; 466 | margin-top: 0; 467 | margin-bottom: 0; 468 | } 469 | 470 | .quote { 471 | border: none; 472 | margin: 20px 0 60px 0; 473 | } 474 | 475 | .quote p { 476 | font-style: italic; 477 | } 478 | 479 | .quote p:before { 480 | content: '“'; 481 | font-size: 50px; 482 | opacity: .15; 483 | position: absolute; 484 | top: -20px; 485 | left: 3px; 486 | } 487 | 488 | .quote p:after { 489 | content: '”'; 490 | font-size: 50px; 491 | opacity: .15; 492 | position: absolute; 493 | bottom: -42px; 494 | right: 3px; 495 | } 496 | 497 | .quote footer { 498 | position: absolute; 499 | bottom: -40px; 500 | right: 0; 501 | } 502 | 503 | .quote footer img { 504 | border-radius: 3px; 505 | } 506 | 507 | .quote footer a { 508 | margin-left: 5px; 509 | vertical-align: middle; 510 | } 511 | 512 | .speech-bubble { 513 | position: relative; 514 | padding: 10px; 515 | background: rgba(0, 0, 0, .04); 516 | border-radius: 5px; 517 | } 518 | 519 | .speech-bubble:after { 520 | content: ''; 521 | position: absolute; 522 | top: 100%; 523 | right: 30px; 524 | border: 13px solid transparent; 525 | border-top-color: rgba(0, 0, 0, .04); 526 | } 527 | 528 | .learn-bar > .learn { 529 | position: absolute; 530 | width: 272px; 531 | top: 8px; 532 | left: -300px; 533 | padding: 10px; 534 | border-radius: 5px; 535 | background-color: rgba(255, 255, 255, .6); 536 | -webkit-transition-property: left; 537 | transition-property: left; 538 | -webkit-transition-duration: 500ms; 539 | transition-duration: 500ms; 540 | } 541 | 542 | @media (min-width: 899px) { 543 | .learn-bar { 544 | width: auto; 545 | margin: 0 0 0 300px; 546 | } 547 | 548 | .learn-bar > .learn { 549 | left: 8px; 550 | } 551 | 552 | .learn-bar #todoapp { 553 | width: 550px; 554 | margin: 130px auto 40px auto; 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /bower_components/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | // Underscore's Template Module 5 | // Courtesy of underscorejs.org 6 | var _ = (function (_) { 7 | _.defaults = function (object) { 8 | if (!object) { 9 | return object; 10 | } 11 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 12 | var iterable = arguments[argsIndex]; 13 | if (iterable) { 14 | for (var key in iterable) { 15 | if (object[key] == null) { 16 | object[key] = iterable[key]; 17 | } 18 | } 19 | } 20 | } 21 | return object; 22 | } 23 | 24 | // By default, Underscore uses ERB-style template delimiters, change the 25 | // following template settings to use alternative delimiters. 26 | _.templateSettings = { 27 | evaluate : /<%([\s\S]+?)%>/g, 28 | interpolate : /<%=([\s\S]+?)%>/g, 29 | escape : /<%-([\s\S]+?)%>/g 30 | }; 31 | 32 | // When customizing `templateSettings`, if you don't want to define an 33 | // interpolation, evaluation or escaping regex, we need one that is 34 | // guaranteed not to match. 35 | var noMatch = /(.)^/; 36 | 37 | // Certain characters need to be escaped so that they can be put into a 38 | // string literal. 39 | var escapes = { 40 | "'": "'", 41 | '\\': '\\', 42 | '\r': 'r', 43 | '\n': 'n', 44 | '\t': 't', 45 | '\u2028': 'u2028', 46 | '\u2029': 'u2029' 47 | }; 48 | 49 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 50 | 51 | // JavaScript micro-templating, similar to John Resig's implementation. 52 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 53 | // and correctly escapes quotes within interpolated code. 54 | _.template = function(text, data, settings) { 55 | var render; 56 | settings = _.defaults({}, settings, _.templateSettings); 57 | 58 | // Combine delimiters into one regular expression via alternation. 59 | var matcher = new RegExp([ 60 | (settings.escape || noMatch).source, 61 | (settings.interpolate || noMatch).source, 62 | (settings.evaluate || noMatch).source 63 | ].join('|') + '|$', 'g'); 64 | 65 | // Compile the template source, escaping string literals appropriately. 66 | var index = 0; 67 | var source = "__p+='"; 68 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 69 | source += text.slice(index, offset) 70 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 71 | 72 | if (escape) { 73 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 74 | } 75 | if (interpolate) { 76 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 77 | } 78 | if (evaluate) { 79 | source += "';\n" + evaluate + "\n__p+='"; 80 | } 81 | index = offset + match.length; 82 | return match; 83 | }); 84 | source += "';\n"; 85 | 86 | // If a variable is not specified, place data values in local scope. 87 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 88 | 89 | source = "var __t,__p='',__j=Array.prototype.join," + 90 | "print=function(){__p+=__j.call(arguments,'');};\n" + 91 | source + "return __p;\n"; 92 | 93 | try { 94 | render = new Function(settings.variable || 'obj', '_', source); 95 | } catch (e) { 96 | e.source = source; 97 | throw e; 98 | } 99 | 100 | if (data) return render(data, _); 101 | var template = function(data) { 102 | return render.call(this, data, _); 103 | }; 104 | 105 | // Provide the compiled function source as a convenience for precompilation. 106 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 107 | 108 | return template; 109 | }; 110 | 111 | return _; 112 | })({}); 113 | 114 | if (location.hostname === 'todomvc.com') { 115 | window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); 116 | } 117 | 118 | function redirect() { 119 | if (location.hostname === 'tastejs.github.io') { 120 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 121 | } 122 | } 123 | 124 | function findRoot() { 125 | var base; 126 | 127 | [/labs/, /\w*-examples/].forEach(function (href) { 128 | var match = location.href.match(href); 129 | 130 | if (!base && match) { 131 | base = location.href.indexOf(match); 132 | } 133 | }); 134 | 135 | return location.href.substr(0, base); 136 | } 137 | 138 | function getFile(file, callback) { 139 | if (!location.host) { 140 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 141 | } 142 | 143 | var xhr = new XMLHttpRequest(); 144 | 145 | xhr.open('GET', findRoot() + file, true); 146 | xhr.send(); 147 | 148 | xhr.onload = function () { 149 | if (xhr.status === 200 && callback) { 150 | callback(xhr.responseText); 151 | } 152 | }; 153 | } 154 | 155 | function Learn(learnJSON, config) { 156 | if (!(this instanceof Learn)) { 157 | return new Learn(learnJSON, config); 158 | } 159 | 160 | var template, framework; 161 | 162 | if (typeof learnJSON !== 'object') { 163 | try { 164 | learnJSON = JSON.parse(learnJSON); 165 | } catch (e) { 166 | return; 167 | } 168 | } 169 | 170 | if (config) { 171 | template = config.template; 172 | framework = config.framework; 173 | } 174 | 175 | if (!template && learnJSON.templates) { 176 | template = learnJSON.templates.todomvc; 177 | } 178 | 179 | if (!framework && document.querySelector('[data-framework]')) { 180 | framework = document.querySelector('[data-framework]').getAttribute('data-framework'); 181 | } 182 | 183 | 184 | if (template && learnJSON[framework]) { 185 | this.frameworkJSON = learnJSON[framework]; 186 | this.template = template; 187 | 188 | this.append(); 189 | } 190 | } 191 | 192 | Learn.prototype.append = function () { 193 | var aside = document.createElement('aside'); 194 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 195 | aside.className = 'learn'; 196 | 197 | // Localize demo links 198 | var demoLinks = aside.querySelectorAll('.demo-link'); 199 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 200 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 201 | }); 202 | 203 | document.body.className = (document.body.className + ' learn-bar').trim(); 204 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 205 | }; 206 | 207 | redirect(); 208 | getFile('learn.json', Learn); 209 | })(); 210 | -------------------------------------------------------------------------------- /bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpmonette/todomvc-mithril/6504b5f9c3589756cdef3bc8028b3458a4ae3d28/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /css/app.css: -------------------------------------------------------------------------------- 1 | /* base.css overrides */ 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mithril • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 |
12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | var app = app || {}; 2 | 3 | (function( window ) { 4 | 'use strict'; 5 | 6 | app.ENTER_KEY = 13; 7 | app.ESC_KEY = 27; 8 | 9 | m.route(document.getElementById('todoapp'), '/', { 10 | '/': app, 11 | '/:filter': app 12 | }); 13 | 14 | })(window); 15 | -------------------------------------------------------------------------------- /js/controllers/todo.js: -------------------------------------------------------------------------------- 1 | var app = app || {}; 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | app.controller = function() { 7 | 8 | this.list = new app.TodoList(); // Todo collection 9 | this.title = m.prop(''); // Temp title placeholder 10 | this.filter = m.prop(m.route.param('filter') || ''); // TodoList filter 11 | 12 | // Add a Todo 13 | this.add = function(title) { 14 | if(this.title()) { 15 | this.list.push(new app.Todo({title: title()})); 16 | this.title(''); 17 | } 18 | }; 19 | 20 | //check whether a todo is visible 21 | this.isVisible = function(todo) { 22 | if(this.filter() == '') 23 | return true; 24 | if (this.filter() == 'active') 25 | return !todo.completed(); 26 | if (this.filter() == 'completed') 27 | return todo.completed(); 28 | } 29 | 30 | this.clearTitle = function() { 31 | this.title('') 32 | } 33 | 34 | // Removing a Todo from the list 35 | this.remove = function(key) { 36 | this.list.splice(key, 1) 37 | } 38 | 39 | // Remove all Todos where Completed == true 40 | this.clearCompleted = function() { 41 | for(var i = this.list.length - 1; i >= 0; i--) { 42 | if (this.list[i].completed()) { 43 | this.list.splice(i, 1); 44 | } 45 | } 46 | } 47 | 48 | // Total amount of Todos completed 49 | this.amountCompleted = function() { 50 | var amount = 0; 51 | 52 | for(var i = 0; i < this.list.length; i++) 53 | if(this.list[i].completed()) 54 | amount++; 55 | 56 | return amount; 57 | } 58 | }; 59 | 60 | })(); 61 | -------------------------------------------------------------------------------- /js/models/todo.js: -------------------------------------------------------------------------------- 1 | var app = app || {}; 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | // Todo Model 7 | app.Todo = function(data) { 8 | this.title = m.prop(data.title); 9 | this.completed = m.prop(false); 10 | }; 11 | 12 | // List of Todos 13 | var list = []; 14 | app.TodoList = function() { 15 | return list; 16 | }; 17 | 18 | })(); 19 | -------------------------------------------------------------------------------- /js/views/footer-view.js: -------------------------------------------------------------------------------- 1 | var app = app || {}; 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | app.footer = function(ctrl) { 7 | return m('footer#footer', [ 8 | m('span#todo-count', [ 9 | m('strong', ctrl.list.length), ' item' + (ctrl.list.length > 1 ? 's' : '') + ' left' 10 | ]), 11 | m('ul#filters', [ 12 | m('li', [ 13 | m('a[href=/]', { 14 | config: m.route, 15 | class: ctrl.filter() == '' ? 'selected' : '' 16 | }, 'All') 17 | ]), 18 | m('li', [ 19 | m('a[href=/active]', { 20 | config: m.route, 21 | class: ctrl.filter() == 'active' ? 'selected' : '' 22 | }, 'Active') 23 | ]), 24 | m('li', [ 25 | m('a[href=/completed]', { 26 | config: m.route, 27 | class: ctrl.filter() == 'completed' ? 'selected' : '' 28 | }, 'Completed') 29 | ]) 30 | ]), 31 | ctrl.amountCompleted() == 0 ? '' : m('button#clear-completed', { 32 | onclick: ctrl.clearCompleted.bind(ctrl) 33 | }, 'Clear completed (' + ctrl.amountCompleted() + ')') 34 | ]); 35 | } 36 | 37 | })(); 38 | -------------------------------------------------------------------------------- /js/views/main-view.js: -------------------------------------------------------------------------------- 1 | var app = app || {}; 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | //view utility 7 | app.watchInput = function(ontype, onenter, onescape) { 8 | return function(e) { 9 | ontype(e) 10 | if (e.keyCode == app.ENTER_KEY) onenter() 11 | if (e.keyCode == app.ESC_KEY) onescape() 12 | } 13 | }; 14 | 15 | 16 | 17 | app.view = function(ctrl) { 18 | return [ 19 | m('header#header', [ 20 | m('h1', 'todos'), 21 | m('input#new-todo[placeholder="What needs to be done?"]', { 22 | onkeypress: app.watchInput( 23 | m.withAttr('value', ctrl.title), 24 | ctrl.add.bind(ctrl, ctrl.title), 25 | ctrl.clearTitle.bind(ctrl) 26 | ), 27 | value: ctrl.title() 28 | }) 29 | ]), 30 | m('section#main', [ 31 | m('input#toggle-all[type=checkbox]'), 32 | m('ul#todo-list', [ 33 | ctrl.list.filter(ctrl.isVisible.bind(ctrl)).map(function(task, index) { 34 | return m('li', { class: task.completed() ? 'completed' : ''}, [ 35 | m('.view', [ 36 | m('input.toggle[type=checkbox]', { 37 | onclick: m.withAttr('checked', task.completed), 38 | checked: task.completed() 39 | }), 40 | m('label', task.title()), 41 | m('button.destroy', { onclick: ctrl.remove.bind(ctrl, index)}) 42 | ]), 43 | m('input.edit') 44 | ]) 45 | }) 46 | ]) 47 | ]), 48 | ctrl.list.length == 0 ? '' : app.footer(ctrl) 49 | ]; 50 | }; 51 | 52 | })(); 53 | -------------------------------------------------------------------------------- /js/views/single-view.js: -------------------------------------------------------------------------------- 1 | var app = app || {}; 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | // Single todo view 7 | app.single = function(ctrl, task, index) { 8 | return m('li', { class: task.completed() ? 'completed' : ''}, [ 9 | m('.view', [ 10 | m('input.toggle[type=checkbox]', { 11 | onclick: m.withAttr('checked', task.completed), 12 | checked: task.completed() 13 | }), 14 | m('label', task.title()), 15 | m('button.destroy', { onclick: ctrl.remove.bind(ctrl, index)}) 16 | ]), 17 | m('input.edit') 18 | ]); 19 | }; 20 | 21 | })(); 22 | --------------------------------------------------------------------------------