Commit 2191e029 authored by Cory Forsyth's avatar Cory Forsyth Committed by Sindre Sorhus

Close GH-670: Ember 1.0.0 and Ember Data 1.0.0-beta.1.

parent 2bb644ad
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
"todomvc-common": "~0.1.4", "todomvc-common": "~0.1.4",
"jquery": "~1.9.1", "jquery": "~1.9.1",
"handlebars": "~1.0.0-rc.3", "handlebars": "~1.0.0-rc.3",
"ember": "~1.0.0-rc.1", "ember": "~1.0.0",
"ember-data": "1.0.0-beta.1",
"ember-localstorage-adapter": "latest" "ember-localstorage-adapter": "latest"
} }
} }
DS.LSSerializer = DS.JSONSerializer.extend({ /*global Ember*/
/*global DS*/
addBelongsTo: function(data, record, key, association) { 'use strict';
data[key] = record.get(key + '.id');
},
addHasMany: function(data, record, key, association) {
data[key] = record.get(key).map(function(record) {
return record.get('id');
});
},
// extract expects a root key, we don't want to save all these keys to
// localStorage so we generate the root keys here
extract: function(loader, json, type, record) {
this._super(loader, this.rootJSON(json, type), type, record);
},
extractMany: function(loader, json, type, records) {
this._super(loader, this.rootJSON(json, type, 'pluralize'), type, records);
},
rootJSON: function(json, type, pluralize) {
var root = this.rootForType(type);
if (pluralize == 'pluralize') { root = this.pluralize(root); }
var rootedJSON = {};
rootedJSON[root] = json;
return rootedJSON;
}
});
DS.LSAdapter = DS.Adapter.extend(Ember.Evented, { DS.LSAdapter = DS.Adapter.extend(Ember.Evented, {
init: function() { init: function () {
this._loadData(); this._loadData();
}, },
generateIdForRecord: function() { generateIdForRecord: function () {
return Math.random().toString(32).slice(2).substr(0,5); return Math.random().toString(32).slice(2).substr(0, 5);
}, },
serializer: DS.LSSerializer.create(), find: function (store, type, id) {
var namespace = this._namespaceForType(type);
find: function(store, type, id) { return Ember.RSVP.resolve(Ember.copy(namespace.records[id]));
var namespace = this._namespaceForType(type); },
this._async(function(){
var copy = Ember.copy(namespace.records[id]); findMany: function (store, type, ids) {
this.didFindRecord(store, type, copy, id); var namespace = this._namespaceForType(type);
}); var results = [];
}, for (var i = 0; i < ids.length; i++) {
results.push(Ember.copy(namespace.records[ids[i]]));
findMany: function(store, type, ids) { }
var namespace = this._namespaceForType(type); return Ember.RSVP.resolve(results);
this._async(function(){ },
var results = [];
for (var i = 0; i < ids.length; i++) {
results.push(Ember.copy(namespace.records[ids[i]]));
}
this.didFindMany(store, type, results);
});
},
// Supports queries that look like this: // Supports queries that look like this:
// //
...@@ -75,141 +40,89 @@ DS.LSAdapter = DS.Adapter.extend(Ember.Evented, { ...@@ -75,141 +40,89 @@ DS.LSAdapter = DS.Adapter.extend(Ember.Evented, {
// match records with "complete: true" and the name "foo" or "bar" // match records with "complete: true" and the name "foo" or "bar"
// //
// { complete: true, name: /foo|bar/ } // { complete: true, name: /foo|bar/ }
findQuery: function(store, type, query, recordArray) { findQuery: function (store, type, query, recordArray) {
var namespace = this._namespaceForType(type); var namespace = this._namespaceForType(type);
this._async(function() { var results = this.query(namespace.records, query);
var results = this.query(namespace.records, query); return Ember.RSVP.resolve(results);
this.didFindQuery(store, type, results, recordArray); },
});
}, query: function (records, query) {
var results = [];
query: function(records, query) { var id, record, property, test, push;
var results = []; for (id in records) {
var id, record, property, test, push; record = records[id];
for (id in records) { for (property in query) {
record = records[id]; test = query[property];
for (property in query) { push = false;
test = query[property]; if (Object.prototype.toString.call(test) === '[object RegExp]') {
push = false; push = test.test(record[property]);
if (Object.prototype.toString.call(test) == '[object RegExp]') { } else {
push = test.test(record[property]); push = record[property] === test;
} else { }
push = record[property] === test; }
} if (push) {
} results.push(record);
if (push) { }
results.push(record); }
} return results;
} },
return results;
}, findAll: function (store, type) {
var namespace = this._namespaceForType(type);
findAll: function(store, type) { var results = [];
var namespace = this._namespaceForType(type); for (var id in namespace.records) {
this._async(function() { results.push(Ember.copy(namespace.records[id]));
var results = []; }
for (var id in namespace.records) { return Ember.RSVP.resolve(results);
results.push(Ember.copy(namespace.records[id])); },
}
this.didFindAll(store, type, results); createRecord: function (store, type, record) {
}); var namespace = this._namespaceForType(type);
}, this._addRecordToNamespace(namespace, record);
this._saveData();
createRecords: function(store, type, records) { return Ember.RSVP.resolve();
var namespace = this._namespaceForType(type); },
records.forEach(function(record) {
this._addRecordToNamespace(namespace, record); updateRecord: function (store, type, record) {
}, this); var namespace = this._namespaceForType(type);
this._async(function() { var id = record.get('id');
this._didSaveRecords(store, type, records); namespace.records[id] = record.toJSON({ includeId: true });
}); this._saveData();
}, return Ember.RSVP.resolve();
},
updateRecords: function(store, type, records) {
var namespace = this._namespaceForType(type); deleteRecord: function (store, type, record) {
this._async(function() { var namespace = this._namespaceForType(type);
records.forEach(function(record) { var id = record.get('id');
var id = record.get('id'); delete namespace.records[id];
namespace.records[id] = record.serialize({includeId:true}); this._saveData();
}, this); return Ember.RSVP.resolve();
this._didSaveRecords(store, type, records); },
});
},
deleteRecords: function(store, type, records) {
var namespace = this._namespaceForType(type);
this._async(function() {
records.forEach(function(record) {
var id = record.get('id');
delete namespace.records[id];
});
this._didSaveRecords(store, type, records);
});
},
dirtyRecordsForHasManyChange: function(dirtySet, parent, relationship) {
dirtySet.add(parent);
},
dirtyRecordsForBelongsToChange: function(dirtySet, child, relationship) {
dirtySet.add(child);
},
// private // private
_getNamespace: function() { _getNamespace: function () {
return this.namespace || 'DS.LSAdapter'; return this.namespace || 'DS.LSAdapter';
}, },
_loadData: function() { _loadData: function () {
var storage = localStorage.getItem(this._getNamespace()); var storage = localStorage.getItem(this._getNamespace());
this._data = storage ? JSON.parse(storage) : {}; this._data = storage ? JSON.parse(storage) : {};
}, },
_didSaveRecords: function(store, type, records) { _saveData: function () {
var success = this._saveData(); localStorage.setItem(this._getNamespace(), JSON.stringify(this._data));
if (success) { },
store.didSaveRecords(records);
} else { _namespaceForType: function (type) {
records.forEach(function(record) { var namespace = type.url || type.toString();
store.recordWasError(record); return this._data[namespace] || (
}); this._data[namespace] = {records: {}}
this.trigger('QUOTA_EXCEEDED_ERR', records); );
} },
},
_addRecordToNamespace: function (namespace, record) {
_saveData: function() { var data = record.serialize({includeId: true});
try { namespace.records[data.id] = data;
localStorage.setItem(this._getNamespace(), JSON.stringify(this._data)); }
return true;
} catch(error) {
if (error.name == 'QUOTA_EXCEEDED_ERR') {
return false;
} else {
throw new Error(error);
}
}
},
_namespaceForType: function(type) {
var namespace = type.url || type.toString();
return this._data[namespace] || (
this._data[namespace] = {records: {}}
);
},
_addRecordToNamespace: function(namespace, record) {
var data = record.serialize({includeId: true});
namespace.records[data.id] = data;
},
_async: function(callback) {
var _this = this;
setTimeout(function(){
Ember.run(_this, callback);
}, 1);
}
}); });
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -11,41 +11,40 @@ ...@@ -11,41 +11,40 @@
<section id="todoapp"> <section id="todoapp">
<header id="header"> <header id="header">
<h1>todos</h1> <h1>todos</h1>
{{view Ember.TextField id="new-todo" placeholder="What needs to be done?" {{input id="new-todo" type="text" value=newTitle action="createTodo" placeholder="What needs to be done?"}}
valueBinding="newTitle" action="createTodo"}}
</header> </header>
{{#if length}} {{#if length}}
<section id="main"> <section id="main">
<ul id="todo-list"> <ul id="todo-list">
{{#each filteredTodos itemController="todo"}} {{#each filteredTodos itemController="todo"}}
<li {{bindAttr class="isCompleted:completed isEditing:editing"}}> <li {{bind-attr class="isCompleted:completed isEditing:editing"}}>
{{#if isEditing}} {{#if isEditing}}
{{view Todos.EditTodoView todoBinding="this"}} {{edit-todo class="edit" value=bufferedTitle focus-out="doneEditing" insert-newline="doneEditing" escape-press="cancelEditing"}}
{{else}} {{else}}
{{view Ember.Checkbox checkedBinding="isCompleted" class="toggle"}} {{input type="checkbox" class="toggle" checked=isCompleted}}
<label {{action "editTodo" on="doubleClick"}}>{{title}}</label> <label {{action "editTodo" on="doubleClick"}}>{{title}}</label>
<button {{action "removeTodo"}} class="destroy"></button> <button {{action "removeTodo"}} class="destroy"></button>
{{/if}} {{/if}}
</li> </li>
{{/each}} {{/each}}
</ul> </ul>
{{view Ember.Checkbox id="toggle-all" checkedBinding="allAreDone"}} {{input type="checkbox" id="toggle-all" checked=allAreDone}}
</section> </section>
<footer id="footer"> <footer id="footer">
<span id="todo-count">{{{remainingFormatted}}}</span> <span id="todo-count">{{{remainingFormatted}}}</span>
<ul id="filters"> <ul id="filters">
<li> <li>
{{#linkTo todos.index activeClass="selected"}}All{{/linkTo}} {{#link-to "todos.index" activeClass="selected"}}All{{/link-to}}
</li> </li>
<li> <li>
{{#linkTo todos.active activeClass="selected"}}Active{{/linkTo}} {{#link-to "todos.active" activeClass="selected"}}Active{{/link-to}}
</li> </li>
<li> <li>
{{#linkTo todos.completed activeClass="selected"}}Completed{{/linkTo}} {{#link-to "todos.completed" activeClass="selected"}}Completed{{/link-to}}
</li> </li>
</ul> </ul>
{{#if hasCompleted}} {{#if hasCompleted}}
<button id="clear-completed" {{action "clearCompleted"}} {{bindAttr class="buttonClass:hidden"}}> <button id="clear-completed" {{action "clearCompleted"}}>
Clear completed ({{completed}}) Clear completed ({{completed}})
</button> </button>
{{/if}} {{/if}}
...@@ -66,13 +65,11 @@ ...@@ -66,13 +65,11 @@
<script src="bower_components/jquery/jquery.js"></script> <script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/handlebars/handlebars.js"></script> <script src="bower_components/handlebars/handlebars.js"></script>
<script src="bower_components/ember/ember.js"></script> <script src="bower_components/ember/ember.js"></script>
<!-- TODO: change out with a component when a built one is available on Bower --> <script src="bower_components/ember-data/ember-data.js"></script>
<script src="js/libs/ember-data.js"></script>
<script src="bower_components/ember-localstorage-adapter/localstorage_adapter.js"></script> <script src="bower_components/ember-localstorage-adapter/localstorage_adapter.js"></script>
<script src="js/app.js"></script> <script src="js/app.js"></script>
<script src="js/router.js"></script> <script src="js/router.js"></script>
<script src="js/models/todo.js"></script> <script src="js/models/todo.js"></script>
<script src="js/models/store.js"></script>
<script src="js/controllers/todos_controller.js"></script> <script src="js/controllers/todos_controller.js"></script>
<script src="js/controllers/todo_controller.js"></script> <script src="js/controllers/todo_controller.js"></script>
<script src="js/views/edit_todo_view.js"></script> <script src="js/views/edit_todo_view.js"></script>
......
/*global Ember */ /*global Ember, DS, Todos:true */
window.Todos = Ember.Application.create(); window.Todos = Ember.Application.create();
Todos.ApplicationAdapter = DS.LSAdapter.extend({
namespace: 'todos-emberjs'
});
/*global Todos Ember */ /*global Todos, Ember */
'use strict'; 'use strict';
Todos.TodoController = Ember.ObjectController.extend({ Todos.TodoController = Ember.ObjectController.extend({
isEditing: false, isEditing: false,
editTodo: function () { // We use the bufferedTitle to store the original value of
this.set('isEditing', true); // the model's title so that we can roll it back later in the
}, // `cancelEditing` action.
bufferedTitle: Ember.computed.oneWay('title'),
removeTodo: function () { actions: {
var todo = this.get('model'); editTodo: function () {
this.set('isEditing', true);
},
todo.deleteRecord(); doneEditing: function () {
todo.get('store').commit(); var bufferedTitle = this.get('bufferedTitle');
if (Ember.isEmpty(bufferedTitle.trim())) {
// The `doneEditing` action gets sent twice when the user hits
// enter (once via 'insert-newline' and once via 'focus-out').
//
// We debounce our call to 'removeTodo' so that it only gets
// sent once.
Ember.run.debounce(this, this.send, 'removeTodo', 0);
} else {
var todo = this.get('model');
todo.set('title', this.get('bufferedTitle'));
todo.save();
}
this.set('isEditing', false);
},
cancelEditing: function () {
this.set('bufferedTitle', this.get('title'));
this.set('isEditing', false);
},
removeTodo: function () {
var todo = this.get('model');
todo.deleteRecord();
todo.save();
}
} }
}); });
/*global Todos Ember */ /*global Todos, Ember */
'use strict'; 'use strict';
Todos.TodosController = Ember.ArrayController.extend({ Todos.TodosController = Ember.ArrayController.extend({
createTodo: function () { actions: {
// Get the todo title set by the "New Todo" text field
var title = this.get('newTitle'); createTodo: function () {
if (!title.trim()) { var title, todo;
return;
} // Get the todo title set by the "New Todo" text field
title = this.get('newTitle');
// Create the new Todo model if (!title.trim()) {
Todos.Todo.createRecord({ return;
title: title, }
isCompleted: false
}); // Create the new Todo model
todo = this.store.createRecord('todo', {
// Clear the "New Todo" text field title: title,
this.set('newTitle', ''); isCompleted: false
});
// Save the new model todo.save();
this.get('store').commit();
}, // Clear the "New Todo" text field
this.set('newTitle', '');
clearCompleted: function () { },
var completed = this.filterProperty('isCompleted', true);
completed.invoke('deleteRecord'); clearCompleted: function () {
var completed = this.filterProperty('isCompleted', true);
this.get('store').commit(); completed.invoke('deleteRecord');
completed.invoke('save');
},
}, },
remaining: function () { remaining: function () {
......
/*global Todos DS */
'use strict';
Todos.Store = DS.Store.extend({
revision: 12,
adapter: 'Todos.LSAdapter'
});
Todos.LSAdapter = DS.LSAdapter.extend({
namespace: 'todos-emberjs'
});
/*global Todos DS Ember */ /*global Todos, DS */
'use strict'; 'use strict';
Todos.Todo = DS.Model.extend({ Todos.Todo = DS.Model.extend({
title: DS.attr('string'), title: DS.attr('string'),
isCompleted: DS.attr('boolean'), isCompleted: DS.attr('boolean'),
todoDidChange: function () { saveWhenCompletedChanged: function () {
Ember.run.once(this, function () { this.save();
this.get('store').commit(); }.observes('isCompleted')
});
}.observes('isCompleted', 'title')
}); });
/*global Todos Ember */ /*global Ember, Todos */
'use strict'; 'use strict';
Todos.Router.map(function () { Todos.Router.map(function () {
...@@ -10,23 +10,20 @@ Todos.Router.map(function () { ...@@ -10,23 +10,20 @@ Todos.Router.map(function () {
Todos.TodosRoute = Ember.Route.extend({ Todos.TodosRoute = Ember.Route.extend({
model: function () { model: function () {
return Todos.Todo.find(); return this.store.find('todo');
} }
}); });
Todos.TodosIndexRoute = Ember.Route.extend({ Todos.TodosIndexRoute = Ember.Route.extend({
setupController: function () { setupController: function () {
var todos = Todos.Todo.find(); this.controllerFor('todos').set('filteredTodos', this.modelFor('todos'));
this.controllerFor('todos').set('filteredTodos', todos);
} }
}); });
Todos.TodosActiveRoute = Ember.Route.extend({ Todos.TodosActiveRoute = Ember.Route.extend({
setupController: function () { setupController: function () {
var todos = Todos.Todo.filter(function (todo) { var todos = this.store.filter('todo', function (todo) {
if (!todo.get('isCompleted')) { return !todo.get('isCompleted');
return true;
}
}); });
this.controllerFor('todos').set('filteredTodos', todos); this.controllerFor('todos').set('filteredTodos', todos);
...@@ -35,10 +32,8 @@ Todos.TodosActiveRoute = Ember.Route.extend({ ...@@ -35,10 +32,8 @@ Todos.TodosActiveRoute = Ember.Route.extend({
Todos.TodosCompletedRoute = Ember.Route.extend({ Todos.TodosCompletedRoute = Ember.Route.extend({
setupController: function () { setupController: function () {
var todos = Todos.Todo.filter(function (todo) { var todos = this.store.filter('todo', function (todo) {
if (todo.get('isCompleted')) { return todo.get('isCompleted');
return true;
}
}); });
this.controllerFor('todos').set('filteredTodos', todos); this.controllerFor('todos').set('filteredTodos', todos);
......
/*global Todos Ember */ /*global Todos, Ember */
'use strict'; 'use strict';
Todos.EditTodoView = Ember.TextField.extend({ Todos.EditTodoView = Ember.TextField.extend({
classNames: ['edit'], focusOnInsert: function () {
valueBinding: 'todo.title',
change: function () {
var value = this.get('value');
if (Ember.isEmpty(value)) {
this.get('controller').removeTodo();
}
},
focusOut: function () {
this.set('controller.isEditing', false);
},
insertNewline: function () {
this.set('controller.isEditing', false);
},
didInsertElement: function () {
this.$().focus(); this.$().focus();
} }.on('didInsertElement')
}); });
Ember.Handlebars.helper('edit-todo', Todos.EditTodoView);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment