Skip to content

Commit

Permalink
Merge pull request #282 from beastridge/gh-pages
Browse files Browse the repository at this point in the history
Thorax
  • Loading branch information
addyosmani committed Sep 23, 2012
2 parents 7982a6f + 0e2547b commit 0ae7682
Show file tree
Hide file tree
Showing 57 changed files with 18,121 additions and 0 deletions.
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ <h2>Labs</h2>
<li>
<a href="http://todomvc.crypho.com" data-source="https://github.com/ggozad/Backbone.xmpp" data-content="Backbone.xmpp is a drop-in replacement for Backbone’s RESTful API, allowing models/collections to be persisted on XMPP Pub-Sub nodes providing real-time updates.">Backbone.xmpp</a>
</li>
<li>
<a href="labs/architecture-examples/thorax" data-source="https://github.com/walmartlabs/thorax" data-content="Thorax is a Backbone superset that integrates deeply with Handlebars to provide easy model and collection binding.">Thorax</a>
</li>
<li>
<a href="labs/architecture-examples/canjs/dojo/" data-source="http://canjs.us" data-content="CanJS with Dojo. CanJS is a client-side, JavaScript framework that makes building rich web applications easy. It provides can.Model (for connecting to RESTful JSON interfaces), can.View (for template loading and caching), can.Observe (for key-value binding), can.EJS (live binding templates), can.Control (declarative event bindings) and can.route (routing support).">CanJS (Dojo)</a>
</li>
Expand Down
16 changes: 16 additions & 0 deletions labs/architecture-examples/thorax/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Thorax TodoMVC
==============
This is a modified version of the Backbone TodoMVC app that uses [Thorax](http://thoraxjs.org).

The Backbone implementation has code to manage the list items which are present on the page. Thorax provides collection bindings with the `collection` helper, which eleminate the need for most of this code:

{{#collection todosCollection filter="filterTodoItem"
item-view="todo-item" tag="ul" id="todo-list"}}

`todosCollection` was specified in js/views/app.js:initialize, all instance variables of the view are automatically made available to the associated template. The `item-view` attribute is optional for collections, but specified here since we want to initialize an `todo-item` view for each item. This class is defined in js/views/todo-item.js and is referenced here by it's `name` attribute which is defined in that file.

The `filter` attribute specifies a function to be called for each model in the collection and hide or show that item depending on wether the function returns true or false. It is called when the collection is first rendered, then as models are added or a model fires a change event. If a `filter` event is triggered on the collection (which it is in routers/router.js:setFilter) it will force the collection to re-filter each model in the colleciton.

In this implementation the `stats` view has it's own view class and is re-rendered instead of the `app` view being re-rendered. Thorax provides the ability to embed views by name or reference with the `view` helper:

{{view "stats"}}
83 changes: 83 additions & 0 deletions labs/architecture-examples/thorax/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Thorax • TodoMVC</title>
<link rel="stylesheet" href="../../../assets/base.css">
<!--[if IE]>
<script src="../../../assets/ie.js"></script>
<![endif]-->
</head>
<body>
<script type="text/template" data-template-name="app">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
</header>
{{^empty todosCollection}}
<section id="main">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
{{#collection todosCollection filter="filterTodoItem" item-view="todo-item" tag="ul" id="todo-list"}}
<div class="view">
<input class="toggle" type="checkbox" {{#if completed}}checked{{/if}}>
<label>{{title}}</label>
<button class="destroy"></button>
</div>
<input class="edit" value="{{title}}">
{{/collection}}
</section>
{{view "stats" tag="footer" id="footer"}}
{{/empty}}
</section>
<div id="info">
<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>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</div>
</script>
<script type="text/template" data-template-name="stats">
<span id="todo-count"><strong>{{remaining}}</strong> {{itemText}} left</span>
<ul id="filters">
<li>
{{#link "/" class="selected"}}All{{/link}}
</li>
<li>
{{#link "/active"}}Active{{/link}}
</li>
<li>
{{#link "/completed"}}Completed{{/link}}
</li>
</ul>
{{#if completed}}
<button id="clear-completed">Clear completed ({{completed}})</button>
{{/if}}
</script>
<script src="../../../assets/base.js"></script>
<script src="../../../assets/jquery.min.js"></script>
<script src="../../../assets/lodash.min.js"></script>
<script src="../../../assets/handlebars.min.js"></script>
<script src="js/lib/backbone.js"></script>
<script src="js/lib/backbone-localstorage.js"></script>
<script src="js/lib/thorax.js"></script>
<script>
// Grab the text from the templates we created above
Thorax.templates = {
app: Handlebars.compile($('script[data-template-name="app"]').html()),
stats: Handlebars.compile($('script[data-template-name="stats"]').html())
};

//initialize the app object
window.app = {};
</script>
<script src="js/models/todo.js"></script>
<script src="js/collections/todos.js"></script>
<script src="js/views/todo-item.js"></script>
<script src="js/views/stats.js"></script>
<script src="js/views/app.js"></script>
<script src="js/routers/router.js"></script>
<script src="js/app.js"></script>
</body>
</html>
8 changes: 8 additions & 0 deletions labs/architecture-examples/thorax/js/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var ENTER_KEY = 13;

$(function() {
Backbone.history.start();
// Kick things off by creating the **App**.
var view = new Thorax.Views['app']();
$('body').append(view.el);
});
47 changes: 47 additions & 0 deletions labs/architecture-examples/thorax/js/collections/todos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
(function() {
'use strict';

// Todo Collection
// ---------------

// The collection of todos is backed by *localStorage* instead of a remote
// server.
var TodoList = Backbone.Collection.extend({

// Reference to this collection's model.
model: window.app.Todo,

// Save all of the todo items under the `"todos"` namespace.
localStorage: new Store('todos-backbone'),

// Filter down the list of all todo items that are finished.
completed: function() {
return this.filter(function( todo ) {
return todo.get('completed');
});
},

// Filter down the list to only todo items that are still not finished.
remaining: function() {
return this.without.apply( this, this.completed() );
},

// We keep the Todos in sequential order, despite being saved by unordered
// GUID in the database. This generates the next order number for new items.
nextOrder: function() {
if ( !this.length ) {
return 1;
}
return this.last().get('order') + 1;
},

// Todos are sorted by their original insertion order.
comparator: function( todo ) {
return todo.get('order');
}
});

// Create our global collection of **Todos**.
window.app.Todos = new TodoList();

}());
84 changes: 84 additions & 0 deletions labs/architecture-examples/thorax/js/lib/backbone-localstorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// A simple module to replace `Backbone.sync` with *localStorage*-based
// persistence. Models are given GUIDS, and saved into a JSON object. Simple
// as that.

// Generate four random hex digits.
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};

// Generate a pseudo-GUID by concatenating random hexadecimal.
function guid() {
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
};

// 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.
var Store = function(name) {
this.name = name;
var store = localStorage.getItem(this.name);
this.data = (store && JSON.parse(store)) || {};
};

_.extend(Store.prototype, {

// Save the current state of the **Store** to *localStorage*.
save: function() {
localStorage.setItem(this.name, JSON.stringify(this.data));
},

// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
// have an id of it's own.
create: function(model) {
if (!model.id) model.id = model.attributes.id = guid();
this.data[model.id] = model;
this.save();
return model;
},

// Update a model by replacing its copy in `this.data`.
update: function(model) {
this.data[model.id] = model;
this.save();
return model;
},

// Retrieve a model from `this.data` by id.
find: function(model) {
return this.data[model.id];
},

// Return the array of all models currently in storage.
findAll: function() {
return _.values(this.data);
},

// Delete a model from `this.data`, returning it.
destroy: function(model) {
delete this.data[model.id];
this.save();
return model;
}

});

// Override `Backbone.sync` to use delegate to the model or collection's
// *localStorage* property, which should be an instance of `Store`.
Backbone.sync = function(method, model, options) {

var resp;
var store = model.localStorage || model.collection.localStorage;

switch (method) {
case "read": resp = model.id ? store.find(model) : store.findAll(); break;
case "create": resp = store.create(model); break;
case "update": resp = store.update(model); break;
case "delete": resp = store.destroy(model); break;
}

if (resp) {
options.success(resp);
} else {
options.error("Record not found");
}
};
Loading

0 comments on commit 0ae7682

Please sign in to comment.