├── readme.md ├── js ├── app.js ├── models │ └── todo.js ├── helpers │ └── pluralize.js ├── views │ └── todo_input_component.js ├── controllers │ ├── todos_list_controller.js │ ├── todos_controller.js │ └── todo_controller.js └── router.js ├── package.json ├── .gitignore └── index.html /readme.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ruby -run -e httpd . -p 9000 4 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | /*global Ember, DS, Todos:true */ 2 | window.Todos = Ember.Application.create(); 3 | 4 | Todos.ApplicationAdapter = DS.RESTAdapter.extend({ 5 | host: 'http://localhost:3000' 6 | }); 7 | -------------------------------------------------------------------------------- /js/models/todo.js: -------------------------------------------------------------------------------- 1 | /*global Todos, DS */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.Todo = DS.Model.extend({ 6 | title: DS.attr('string'), 7 | isCompleted: DS.attr('boolean') 8 | }); 9 | })(); 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "todomvc-app-css": "^1.0.0", 5 | "todomvc-common": "^1.0.1", 6 | "jquery": "^2.1.0", 7 | "handlebars": "^2.0.0", 8 | "ember": "components/ember#1.10.0-beta.3", 9 | "ember-localstorage-adapter": "^0.5.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /js/helpers/pluralize.js: -------------------------------------------------------------------------------- 1 | /*global Ember */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Ember.Handlebars.helper('pluralize', function (singular, count) { 6 | /* From Ember-Data */ 7 | var inflector = Ember.Inflector.inflector; 8 | 9 | return count === 1 ? singular : inflector.pluralize(singular); 10 | }); 11 | })(); 12 | -------------------------------------------------------------------------------- /js/views/todo_input_component.js: -------------------------------------------------------------------------------- 1 | /*global Todos, Ember */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.TodoInputComponent = Ember.TextField.extend({ 6 | focusOnInsert: function () { 7 | // Re-set input value to get rid of a reduntant text selection 8 | this.$().val(this.$().val()); 9 | this.$().focus(); 10 | }.on('didInsertElement') 11 | }); 12 | })(); 13 | -------------------------------------------------------------------------------- /js/controllers/todos_list_controller.js: -------------------------------------------------------------------------------- 1 | /*global Todos, Ember */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.TodosListController = Ember.ArrayController.extend({ 6 | needs: ['todos'], 7 | allTodos: Ember.computed.alias('controllers.todos'), 8 | itemController: 'todo', 9 | canToggle: function () { 10 | var anyTodos = this.get('allTodos.length'); 11 | var isEditing = this.isAny('isEditing'); 12 | 13 | return anyTodos && !isEditing; 14 | }.property('allTodos.length', '@each.isEditing') 15 | }); 16 | })(); 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/.bin 2 | node_modules/jquery 3 | !node_modules/jquery/dist/jquery.js 4 | 5 | node_modules/components-ember 6 | !node_modules/components-ember/ember.js 7 | 8 | node_modules/ember-data 9 | !node_modules/ember-data.js 10 | 11 | node_modules/ember-localstorage-adapter 12 | !node_modules/ember-localstorage-adapter/localstorage_adapter.js 13 | 14 | node_modules/handlebars 15 | !node_modules/handlebars/dist/handlebars.js 16 | 17 | node_modules/todomvc-app-css 18 | !node_modules/todomvc-app-css/index.css 19 | 20 | node_modules/todomvc-common 21 | !node_modules/todomvc-common/base.css 22 | !node_modules/todomvc-common/base.js 23 | -------------------------------------------------------------------------------- /js/router.js: -------------------------------------------------------------------------------- 1 | /*global Ember, Todos */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.Router.map(function () { 6 | this.resource('todos', { path: '/' }, function () { 7 | this.route('active'); 8 | this.route('completed'); 9 | }); 10 | }); 11 | 12 | Todos.TodosRoute = Ember.Route.extend({ 13 | model: function () { 14 | return this.store.find('todo'); 15 | } 16 | }); 17 | 18 | Todos.TodosIndexRoute = Todos.TodosRoute.extend({ 19 | templateName: 'todo-list', 20 | controllerName: 'todos-list' 21 | }); 22 | 23 | Todos.TodosActiveRoute = Todos.TodosIndexRoute.extend({ 24 | model: function () { 25 | return this.store.filter('todo', function (todo) { 26 | return !todo.get('isCompleted'); 27 | }); 28 | } 29 | }); 30 | 31 | Todos.TodosCompletedRoute = Todos.TodosIndexRoute.extend({ 32 | model: function () { 33 | return this.store.filter('todo', function (todo) { 34 | return todo.get('isCompleted'); 35 | }); 36 | } 37 | }); 38 | })(); 39 | -------------------------------------------------------------------------------- /js/controllers/todos_controller.js: -------------------------------------------------------------------------------- 1 | /*global Todos, Ember */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.TodosController = Ember.ArrayController.extend({ 6 | actions: { 7 | createTodo: function () { 8 | var title, todo; 9 | 10 | // Get the todo title set by the "New Todo" text field 11 | title = this.get('newTitle').trim(); 12 | if (!title) { 13 | return; 14 | } 15 | 16 | // Create the new Todo model 17 | todo = this.store.createRecord('todo', { 18 | title: title, 19 | isCompleted: false 20 | }); 21 | todo.save(); 22 | 23 | // Clear the "New Todo" text field 24 | this.set('newTitle', ''); 25 | }, 26 | 27 | clearCompleted: function () { 28 | var completed = this.get('completed'); 29 | completed.invoke('deleteRecord'); 30 | completed.invoke('save'); 31 | } 32 | }, 33 | 34 | /* properties */ 35 | 36 | remaining: Ember.computed.filterBy('model', 'isCompleted', false), 37 | completed: Ember.computed.filterBy('model', 'isCompleted', true), 38 | 39 | allAreDone: function (key, value) { 40 | if (value !== undefined) { 41 | this.setEach('isCompleted', value); 42 | return value; 43 | } else { 44 | var length = this.get('length'); 45 | var completedLength = this.get('completed.length'); 46 | 47 | return length > 0 && length === completedLength; 48 | } 49 | }.property('length', 'completed.length') 50 | }); 51 | })(); 52 | -------------------------------------------------------------------------------- /js/controllers/todo_controller.js: -------------------------------------------------------------------------------- 1 | /*global Todos, Ember */ 2 | (function () { 3 | 'use strict'; 4 | Todos.TodoController = Ember.ObjectController.extend({ 5 | isEditing: false, 6 | 7 | // We use the bufferedTitle to store the original value of 8 | // the model's title so that we can roll it back later in the 9 | // `cancelEditing` action. 10 | bufferedTitle: Ember.computed.oneWay('title'), 11 | 12 | actions: { 13 | editTodo: function () { 14 | this.set('isEditing', true); 15 | }, 16 | 17 | doneEditing: function () { 18 | var bufferedTitle = this.get('bufferedTitle').trim(); 19 | 20 | if (Ember.isEmpty(bufferedTitle)) { 21 | // The `doneEditing` action gets sent twice when the user hits 22 | // enter (once via 'insert-newline' and once via 'focus-out'). 23 | // 24 | // We debounce our call to 'removeTodo' so that it only gets 25 | // made once. 26 | Ember.run.debounce(this, 'removeTodo', 0); 27 | } else { 28 | var todo = this.get('model'); 29 | todo.set('title', bufferedTitle); 30 | todo.save(); 31 | } 32 | 33 | // Re-set our newly edited title to persist its trimmed version 34 | this.set('bufferedTitle', bufferedTitle); 35 | this.set('isEditing', false); 36 | }, 37 | 38 | cancelEditing: function () { 39 | this.set('bufferedTitle', this.get('title')); 40 | this.set('isEditing', false); 41 | }, 42 | 43 | removeTodo: function () { 44 | this.removeTodo(); 45 | } 46 | }, 47 | 48 | removeTodo: function () { 49 | var todo = this.get('model'); 50 | 51 | todo.deleteRecord(); 52 | todo.save(); 53 | }, 54 | 55 | saveWhenCompleted: function () { 56 | this.get('model').save(); 57 | }.observes('isCompleted') 58 | }); 59 | })(); 60 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |