Commit 440603b4 authored by Ryan Eastridge's avatar Ryan Eastridge

Update thorax_lumbar src from thorax

parent a1081622
...@@ -2,6 +2,8 @@ var ENTER_KEY = 13; ...@@ -2,6 +2,8 @@ var ENTER_KEY = 13;
$(function() { $(function() {
// Kick things off by creating the **App**. // Kick things off by creating the **App**.
var view = new Thorax.Views['app'](); var view = new Thorax.Views['app']({
$('body').append(view.el); collection: window.app.Todos
});
view.appendTo('body');
}); });
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
// The collection of todos is backed by *localStorage* instead of a remote // The collection of todos is backed by *localStorage* instead of a remote
// server. // server.
var TodoList = Backbone.Collection.extend({ var TodoList = Thorax.Collection.extend({
// Reference to this collection's model. // Reference to this collection's model.
model: window.app.Todo, model: window.app.Todo,
// Save all of the todo items under the `"todos"` namespace. // Save all of the todo items under the `"todos"` namespace.
localStorage: new Store('todos-backbone'), localStorage: new Store('todos-backbone-thorax'),
// Filter down the list of all todo items that are finished. // Filter down the list of all todo items that are finished.
completed: function() { completed: function() {
...@@ -44,4 +44,7 @@ ...@@ -44,4 +44,7 @@
// Create our global collection of **Todos**. // Create our global collection of **Todos**.
window.app.Todos = new TodoList(); window.app.Todos = new TodoList();
// Ensure that we always have data available
window.app.Todos.fetch();
}()); }());
/**
* Backbone localStorage Adapter
* https://github.com/jeromegn/Backbone.localStorage
*/
(function(_, Backbone) {
// A simple module to replace `Backbone.sync` with *localStorage*-based // A simple module to replace `Backbone.sync` with *localStorage*-based
// persistence. Models are given GUIDS, and saved into a JSON object. Simple // persistence. Models are given GUIDS, and saved into a JSON object. Simple
// as that. // as that.
// Hold reference to Underscore.js and Backbone.js in the closure in order
// to make things work even if they are removed from the global namespace
// Generate four random hex digits. // Generate four random hex digits.
function S4() { function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1); return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
...@@ -14,71 +23,108 @@ function guid() { ...@@ -14,71 +23,108 @@ function guid() {
// Our Store is represented by a single JS object in *localStorage*. Create it // Our Store is represented by a single JS object in *localStorage*. Create it
// with a meaningful name, like the name you'd give a table. // with a meaningful name, like the name you'd give a table.
var Store = function(name) { // window.Store is deprectated, use Backbone.LocalStorage instead
Backbone.LocalStorage = window.Store = function(name) {
this.name = name; this.name = name;
var store = localStorage.getItem(this.name); var store = this.localStorage().getItem(this.name);
this.data = (store && JSON.parse(store)) || {}; this.records = (store && store.split(",")) || [];
}; };
_.extend(Store.prototype, { _.extend(Backbone.LocalStorage.prototype, {
// Save the current state of the **Store** to *localStorage*. // Save the current state of the **Store** to *localStorage*.
save: function() { save: function() {
localStorage.setItem(this.name, JSON.stringify(this.data)); this.localStorage().setItem(this.name, this.records.join(","));
}, },
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already // Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
// have an id of it's own. // have an id of it's own.
create: function(model) { create: function(model) {
if (!model.id) model.id = model.attributes.id = guid(); if (!model.id) {
this.data[model.id] = model; model.id = guid();
model.set(model.idAttribute, model.id);
}
this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
this.records.push(model.id.toString());
this.save(); this.save();
return model; return model.toJSON();
}, },
// Update a model by replacing its copy in `this.data`. // Update a model by replacing its copy in `this.data`.
update: function(model) { update: function(model) {
this.data[model.id] = model; this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
this.save(); if (!_.include(this.records, model.id.toString())) this.records.push(model.id.toString()); this.save();
return model; return model.toJSON();
}, },
// Retrieve a model from `this.data` by id. // Retrieve a model from `this.data` by id.
find: function(model) { find: function(model) {
return this.data[model.id]; return JSON.parse(this.localStorage().getItem(this.name+"-"+model.id));
}, },
// Return the array of all models currently in storage. // Return the array of all models currently in storage.
findAll: function() { findAll: function() {
return _.values(this.data); return _(this.records).chain()
.map(function(id){return JSON.parse(this.localStorage().getItem(this.name+"-"+id));}, this)
.compact()
.value();
}, },
// Delete a model from `this.data`, returning it. // Delete a model from `this.data`, returning it.
destroy: function(model) { destroy: function(model) {
delete this.data[model.id]; this.localStorage().removeItem(this.name+"-"+model.id);
this.records = _.reject(this.records, function(record_id){return record_id == model.id.toString();});
this.save(); this.save();
return model; return model;
},
localStorage: function() {
return localStorage;
} }
}); });
// Override `Backbone.sync` to use delegate to the model or collection's // localSync delegate to the model or collection's
// *localStorage* property, which should be an instance of `Store`. // *localStorage* property, which should be an instance of `Store`.
Backbone.sync = function(method, model, options) { // window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead
Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) {
var resp;
var store = model.localStorage || model.collection.localStorage; var store = model.localStorage || model.collection.localStorage;
var resp, syncDfd = $.Deferred && $.Deferred(); //If $ is having Deferred - use it.
switch (method) { switch (method) {
case "read": resp = model.id ? store.find(model) : store.findAll(); break; case "read": resp = model.id != undefined ? store.find(model) : store.findAll(); break;
case "create": resp = store.create(model); break; case "create": resp = store.create(model); break;
case "update": resp = store.update(model); break; case "update": resp = store.update(model); break;
case "delete": resp = store.destroy(model); break; case "delete": resp = store.destroy(model); break;
} }
if (resp) { if (resp) {
options.success(resp); if (options && options.success) options.success(resp);
if (syncDfd) syncDfd.resolve();
} else { } else {
options.error("Record not found"); if (options && options.error) options.error("Record not found");
if (syncDfd) syncDfd.reject();
} }
return syncDfd && syncDfd.promise();
};
Backbone.ajaxSync = Backbone.sync;
Backbone.getSyncMethod = function(model) {
if(model.localStorage || (model.collection && model.collection.localStorage))
{
return Backbone.localSync;
}
return Backbone.ajaxSync;
}; };
// Override 'Backbone.sync' to default to localSync,
// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
Backbone.sync = function(method, model, options) {
return Backbone.getSyncMethod(model).apply(this, [method, model, options]);
};
})(_, Backbone);
\ No newline at end of file
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
// ---------- // ----------
// Our basic **Todo** model has `title`, `order`, and `completed` attributes. // Our basic **Todo** model has `title`, `order`, and `completed` attributes.
window.app.Todo = Backbone.Model.extend({ window.app.Todo = Thorax.Model.extend({
// Default attributes for the todo // Default attributes for the todo
// and ensure that each todo created has `title` and `completed` keys. // and ensure that each todo created has `title` and `completed` keys.
......
...@@ -4,43 +4,32 @@ $(function( $ ) { ...@@ -4,43 +4,32 @@ $(function( $ ) {
// The Application // The Application
// --------------- // ---------------
// Our overall **AppView** is the top-level piece of UI. // This view is the top-level piece of UI.
Thorax.View.extend({ Thorax.View.extend({
// This will assign the template Thorax.templates['app'] to the view and // Setting a name will assign the template Thorax.templates['app']
// create a view class at Thorax.Views['app'] // to the view and create a view class at Thorax.Views['app']
name: 'app', name: 'app',
// Delegated events for creating new items, and clearing completed ones. // Delegated events for creating new items, and clearing completed ones.
events: { events: {
'keypress #new-todo': 'createOnEnter', 'keypress #new-todo': 'createOnEnter',
'click #toggle-all': 'toggleAllComplete', 'click #toggle-all': 'toggleAllComplete',
// The collection helper in the template will bind the collection // Any events specified in the collection hash will be bound to the
// to the view. Any events in this hash will be bound to the // collection with `listenTo`. The collection was set in js/app.js
// collection.
collection: { collection: {
all: 'toggleToggleAllButton' 'change:completed': 'toggleToggleAllButton',
filter: 'toggleToggleAllButton'
}, },
rendered: 'toggleToggleAllButton' rendered: 'toggleToggleAllButton'
}, },
// Unless the "context" method is overriden any attributes on the view
// will be availble to the context / scope of the template, make the
// global Todos collection available to the template.
// Load any preexisting todos that might be saved in *localStorage*.
initialize: function() {
this.todosCollection = window.app.Todos;
this.todosCollection.fetch();
this.render();
},
toggleToggleAllButton: function() { toggleToggleAllButton: function() {
this.$('#toggle-all').attr('checked', !this.todosCollection.remaining().length); this.$('#toggle-all')[0].checked = !this.collection.remaining().length;
}, },
// This function is specified in the collection helper as the filter // When this function is specified, items will only be shown
// and will be called each time a model changes, or for each item // when this function returns true
// when the collection is rendered itemFilter: function(model) {
filterTodoItem: function(model) {
return model.isVisible(); return model.isVisible();
}, },
...@@ -48,7 +37,7 @@ $(function( $ ) { ...@@ -48,7 +37,7 @@ $(function( $ ) {
newAttributes: function() { newAttributes: function() {
return { return {
title: this.$('#new-todo').val().trim(), title: this.$('#new-todo').val().trim(),
order: window.app.Todos.nextOrder(), order: this.collection.nextOrder(),
completed: false completed: false
}; };
}, },
...@@ -60,16 +49,15 @@ $(function( $ ) { ...@@ -60,16 +49,15 @@ $(function( $ ) {
return; return;
} }
window.app.Todos.create( this.newAttributes() ); this.collection.create( this.newAttributes() );
this.$('#new-todo').val(''); this.$('#new-todo').val('');
}, },
toggleAllComplete: function() { toggleAllComplete: function() {
var completed = this.$('#toggle-all')[0].checked; var completed = this.$('#toggle-all')[0].checked;
this.collection.each(function( todo ) {
window.app.Todos.each(function( todo ) {
todo.save({ todo.save({
'completed': completed completed: completed
}); });
}); });
} }
......
...@@ -13,9 +13,9 @@ Thorax.View.extend({ ...@@ -13,9 +13,9 @@ Thorax.View.extend({
// Whenever the Todos collection changes re-render the stats // Whenever the Todos collection changes re-render the stats
// render() needs to be called with no arguments, otherwise calling // render() needs to be called with no arguments, otherwise calling
// it with arguments will insert the arguments as content // it with arguments will insert the arguments as content
window.app.Todos.on('all', _.debounce(function() { this.listenTo(window.app.Todos, 'all', _.debounce(function() {
this.render(); this.render();
}), this); }));
}, },
// Clear all completed todo items, destroying their models. // Clear all completed todo items, destroying their models.
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
<h1>todos</h1> <h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus> <input id="new-todo" placeholder="What needs to be done?" autofocus>
</header> </header>
{{^empty todosCollection}} {{^empty collection}}
<section id="main"> <section id="main">
<input id="toggle-all" type="checkbox"> <input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label> <label for="toggle-all">Mark all as complete</label>
{{#collection todosCollection filter="filterTodoItem" item-view="todo-item" tag="ul" id="todo-list"}} {{#collection item-view="todo-item" tag="ul" id="todo-list"}}
<div class="view"> <div class="view">
<input class="toggle" type="checkbox" {{#if completed}}checked{{/if}}> <input class="toggle" type="checkbox" {{#if completed}}checked="checked"{{/if}}>
<label>{{title}}</label> <label>{{title}}</label>
<button class="destroy"></button> <button class="destroy"></button>
</div> </div>
...@@ -21,6 +21,6 @@ ...@@ -21,6 +21,6 @@
</section> </section>
<div id="info"> <div id="info">
<p>Double-click to edit a todo</p> <p>Double-click to edit a todo</p>
<p>Written by <a href="https://github.com/addyosmani">Addy Osmani</a> &amp; <a href="https://github.com/beastridge">Ryan Eastridge</a></p> <p>Written by <a href="https://github.com/addyosmani">Addy Osmani</a> &amp; <a href="https://github.com/eastridge">Ryan Eastridge</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</div> </div>
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