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 @@
"todomvc-common": "~0.1.4",
"jquery": "~1.9.1",
"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"
}
}
DS.LSSerializer = DS.JSONSerializer.extend({
addBelongsTo: function(data, record, key, association) {
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;
}
});
/*global Ember*/
/*global DS*/
'use strict';
DS.LSAdapter = DS.Adapter.extend(Ember.Evented, {
init: function() {
this._loadData();
},
generateIdForRecord: function() {
return Math.random().toString(32).slice(2).substr(0,5);
},
serializer: DS.LSSerializer.create(),
find: function(store, type, id) {
var namespace = this._namespaceForType(type);
this._async(function(){
var copy = Ember.copy(namespace.records[id]);
this.didFindRecord(store, type, copy, id);
});
},
findMany: function(store, type, ids) {
var namespace = this._namespaceForType(type);
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);
});
},
init: function () {
this._loadData();
},
generateIdForRecord: function () {
return Math.random().toString(32).slice(2).substr(0, 5);
},
find: function (store, type, id) {
var namespace = this._namespaceForType(type);
return Ember.RSVP.resolve(Ember.copy(namespace.records[id]));
},
findMany: function (store, type, ids) {
var namespace = this._namespaceForType(type);
var results = [];
for (var i = 0; i < ids.length; i++) {
results.push(Ember.copy(namespace.records[ids[i]]));
}
return Ember.RSVP.resolve(results);
},
// Supports queries that look like this:
//
......@@ -75,141 +40,89 @@ DS.LSAdapter = DS.Adapter.extend(Ember.Evented, {
// match records with "complete: true" and the name "foo" or "bar"
//
// { complete: true, name: /foo|bar/ }
findQuery: function(store, type, query, recordArray) {
var namespace = this._namespaceForType(type);
this._async(function() {
var results = this.query(namespace.records, query);
this.didFindQuery(store, type, results, recordArray);
});
},
query: function(records, query) {
var results = [];
var id, record, property, test, push;
for (id in records) {
record = records[id];
for (property in query) {
test = query[property];
push = false;
if (Object.prototype.toString.call(test) == '[object RegExp]') {
push = test.test(record[property]);
} else {
push = record[property] === test;
}
}
if (push) {
results.push(record);
}
}
return results;
},
findAll: function(store, type) {
var namespace = this._namespaceForType(type);
this._async(function() {
var results = [];
for (var id in namespace.records) {
results.push(Ember.copy(namespace.records[id]));
}
this.didFindAll(store, type, results);
});
},
createRecords: function(store, type, records) {
var namespace = this._namespaceForType(type);
records.forEach(function(record) {
this._addRecordToNamespace(namespace, record);
}, this);
this._async(function() {
this._didSaveRecords(store, type, records);
});
},
updateRecords: function(store, type, records) {
var namespace = this._namespaceForType(type);
this._async(function() {
records.forEach(function(record) {
var id = record.get('id');
namespace.records[id] = record.serialize({includeId:true});
}, this);
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);
},
findQuery: function (store, type, query, recordArray) {
var namespace = this._namespaceForType(type);
var results = this.query(namespace.records, query);
return Ember.RSVP.resolve(results);
},
query: function (records, query) {
var results = [];
var id, record, property, test, push;
for (id in records) {
record = records[id];
for (property in query) {
test = query[property];
push = false;
if (Object.prototype.toString.call(test) === '[object RegExp]') {
push = test.test(record[property]);
} else {
push = record[property] === test;
}
}
if (push) {
results.push(record);
}
}
return results;
},
findAll: function (store, type) {
var namespace = this._namespaceForType(type);
var results = [];
for (var id in namespace.records) {
results.push(Ember.copy(namespace.records[id]));
}
return Ember.RSVP.resolve(results);
},
createRecord: function (store, type, record) {
var namespace = this._namespaceForType(type);
this._addRecordToNamespace(namespace, record);
this._saveData();
return Ember.RSVP.resolve();
},
updateRecord: function (store, type, record) {
var namespace = this._namespaceForType(type);
var id = record.get('id');
namespace.records[id] = record.toJSON({ includeId: true });
this._saveData();
return Ember.RSVP.resolve();
},
deleteRecord: function (store, type, record) {
var namespace = this._namespaceForType(type);
var id = record.get('id');
delete namespace.records[id];
this._saveData();
return Ember.RSVP.resolve();
},
// private
_getNamespace: function() {
return this.namespace || 'DS.LSAdapter';
},
_loadData: function() {
var storage = localStorage.getItem(this._getNamespace());
this._data = storage ? JSON.parse(storage) : {};
},
_didSaveRecords: function(store, type, records) {
var success = this._saveData();
if (success) {
store.didSaveRecords(records);
} else {
records.forEach(function(record) {
store.recordWasError(record);
});
this.trigger('QUOTA_EXCEEDED_ERR', records);
}
},
_saveData: function() {
try {
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);
}
_getNamespace: function () {
return this.namespace || 'DS.LSAdapter';
},
_loadData: function () {
var storage = localStorage.getItem(this._getNamespace());
this._data = storage ? JSON.parse(storage) : {};
},
_saveData: function () {
localStorage.setItem(this._getNamespace(), JSON.stringify(this._data));
},
_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;
}
});
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -11,41 +11,40 @@
<section id="todoapp">
<header id="header">
<h1>todos</h1>
{{view Ember.TextField id="new-todo" placeholder="What needs to be done?"
valueBinding="newTitle" action="createTodo"}}
{{input id="new-todo" type="text" value=newTitle action="createTodo" placeholder="What needs to be done?"}}
</header>
{{#if length}}
<section id="main">
<ul id="todo-list">
{{#each filteredTodos itemController="todo"}}
<li {{bindAttr class="isCompleted:completed isEditing:editing"}}>
<li {{bind-attr class="isCompleted:completed isEditing:editing"}}>
{{#if isEditing}}
{{view Todos.EditTodoView todoBinding="this"}}
{{edit-todo class="edit" value=bufferedTitle focus-out="doneEditing" insert-newline="doneEditing" escape-press="cancelEditing"}}
{{else}}
{{view Ember.Checkbox checkedBinding="isCompleted" class="toggle"}}
{{input type="checkbox" class="toggle" checked=isCompleted}}
<label {{action "editTodo" on="doubleClick"}}>{{title}}</label>
<button {{action "removeTodo"}} class="destroy"></button>
{{/if}}
</li>
{{/each}}
</ul>
{{view Ember.Checkbox id="toggle-all" checkedBinding="allAreDone"}}
{{input type="checkbox" id="toggle-all" checked=allAreDone}}
</section>
<footer id="footer">
<span id="todo-count">{{{remainingFormatted}}}</span>
<ul id="filters">
<li>
{{#linkTo todos.index activeClass="selected"}}All{{/linkTo}}
{{#link-to "todos.index" activeClass="selected"}}All{{/link-to}}
</li>
<li>
{{#linkTo todos.active activeClass="selected"}}Active{{/linkTo}}
{{#link-to "todos.active" activeClass="selected"}}Active{{/link-to}}
</li>
<li>
{{#linkTo todos.completed activeClass="selected"}}Completed{{/linkTo}}
{{#link-to "todos.completed" activeClass="selected"}}Completed{{/link-to}}
</li>
</ul>
{{#if hasCompleted}}
<button id="clear-completed" {{action "clearCompleted"}} {{bindAttr class="buttonClass:hidden"}}>
<button id="clear-completed" {{action "clearCompleted"}}>
Clear completed ({{completed}})
</button>
{{/if}}
......@@ -66,13 +65,11 @@
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/handlebars/handlebars.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="js/libs/ember-data.js"></script>
<script src="bower_components/ember-data/ember-data.js"></script>
<script src="bower_components/ember-localstorage-adapter/localstorage_adapter.js"></script>
<script src="js/app.js"></script>
<script src="js/router.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/todo_controller.js"></script>
<script src="js/views/edit_todo_view.js"></script>
......
/*global Ember */
/*global Ember, DS, Todos:true */
window.Todos = Ember.Application.create();
Todos.ApplicationAdapter = DS.LSAdapter.extend({
namespace: 'todos-emberjs'
});
/*global Todos Ember */
/*global Todos, Ember */
'use strict';
Todos.TodoController = Ember.ObjectController.extend({
isEditing: false,
editTodo: function () {
this.set('isEditing', true);
},
// We use the bufferedTitle to store the original value of
// the model's title so that we can roll it back later in the
// `cancelEditing` action.
bufferedTitle: Ember.computed.oneWay('title'),
removeTodo: function () {
var todo = this.get('model');
actions: {
editTodo: function () {
this.set('isEditing', true);
},
todo.deleteRecord();
todo.get('store').commit();
doneEditing: function () {
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';
Todos.TodosController = Ember.ArrayController.extend({
createTodo: function () {
// Get the todo title set by the "New Todo" text field
var title = this.get('newTitle');
if (!title.trim()) {
return;
}
// Create the new Todo model
Todos.Todo.createRecord({
title: title,
isCompleted: false
});
// Clear the "New Todo" text field
this.set('newTitle', '');
// Save the new model
this.get('store').commit();
},
clearCompleted: function () {
var completed = this.filterProperty('isCompleted', true);
completed.invoke('deleteRecord');
this.get('store').commit();
actions: {
createTodo: function () {
var title, todo;
// Get the todo title set by the "New Todo" text field
title = this.get('newTitle');
if (!title.trim()) {
return;
}
// Create the new Todo model
todo = this.store.createRecord('todo', {
title: title,
isCompleted: false
});
todo.save();
// Clear the "New Todo" text field
this.set('newTitle', '');
},
clearCompleted: function () {
var completed = this.filterProperty('isCompleted', true);
completed.invoke('deleteRecord');
completed.invoke('save');
},
},
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';
Todos.Todo = DS.Model.extend({
title: DS.attr('string'),
isCompleted: DS.attr('boolean'),
todoDidChange: function () {
Ember.run.once(this, function () {
this.get('store').commit();
});
}.observes('isCompleted', 'title')
saveWhenCompletedChanged: function () {
this.save();
}.observes('isCompleted')
});
/*global Todos Ember */
/*global Ember, Todos */
'use strict';
Todos.Router.map(function () {
......@@ -10,23 +10,20 @@ Todos.Router.map(function () {
Todos.TodosRoute = Ember.Route.extend({
model: function () {
return Todos.Todo.find();
return this.store.find('todo');
}
});
Todos.TodosIndexRoute = Ember.Route.extend({
setupController: function () {
var todos = Todos.Todo.find();
this.controllerFor('todos').set('filteredTodos', todos);
this.controllerFor('todos').set('filteredTodos', this.modelFor('todos'));
}
});
Todos.TodosActiveRoute = Ember.Route.extend({
setupController: function () {
var todos = Todos.Todo.filter(function (todo) {
if (!todo.get('isCompleted')) {
return true;
}
var todos = this.store.filter('todo', function (todo) {
return !todo.get('isCompleted');
});
this.controllerFor('todos').set('filteredTodos', todos);
......@@ -35,10 +32,8 @@ Todos.TodosActiveRoute = Ember.Route.extend({
Todos.TodosCompletedRoute = Ember.Route.extend({
setupController: function () {
var todos = Todos.Todo.filter(function (todo) {
if (todo.get('isCompleted')) {
return true;
}
var todos = this.store.filter('todo', function (todo) {
return todo.get('isCompleted');
});
this.controllerFor('todos').set('filteredTodos', todos);
......
/*global Todos Ember */
/*global Todos, Ember */
'use strict';
Todos.EditTodoView = Ember.TextField.extend({
classNames: ['edit'],
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 () {
focusOnInsert: function () {
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