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.
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