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