Commit d132997d authored by Ryan Eastridge's avatar Ryan Eastridge

add Thorax + Require example

parent c7284e71
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Thorax + RequireJS • TodoMVC</title>
<link rel="stylesheet" href="../../../assets/base.css">
<script src="../../../assets/base.js"></script>
<script data-main="js/main" src="js/lib/require/require.js"></script>
<!--[if IE]>
<script src="../../../assets/ie.js"></script>
<![endif]-->
</head>
<body>
</body>
</html>
define([
'underscore',
'backbone',
'lib/backbone/localstorage',
'models/todo'
], function( _, Backbone, Store, Todo ) {
var TodoList = Backbone.Collection.extend({
// Reference to this collection's model.
model: 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');
}
});
return new TodoList();
});
define([], function() {
return {
// Which filter are we using?
TodoFilter: '', // empty, active, completed
// What is the enter key constant?
ENTER_KEY: 13
};
});
define(["underscore","backbone"],function(_,Backbone){function S4(){return((1+Math.random())*65536|0).toString(16).substring(1)}function guid(){return S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()}var Store=function(name){this.name=name;var store=localStorage.getItem(this.name);this.data=store&&JSON.parse(store)||{}};_.extend(Store.prototype,{save:function(){localStorage.setItem(this.name,JSON.stringify(this.data))},create:function(model){if(!model.id)model.id=model.attributes.id=guid();
this.data[model.id]=model;this.save();return model},update:function(model){this.data[model.id]=model;this.save();return model},find:function(model){return this.data[model.id]},findAll:function(){return _.values(this.data)},destroy:function(model){delete this.data[model.id];this.save();return model}});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")};return Store});
This diff is collapsed.
This diff is collapsed.
// Require.js allows us to configure shortcut alias
require.config({
// The shim config allows us to configure dependencies for
// scripts that do not call define() to register a module
shim: {
'underscore': {
exports: '_'
},
'backbone': {
deps: [
'underscore',
'jquery'
],
exports: 'Backbone'
},
'thorax': {
deps: [
'underscore',
'backbone',
'jquery',
'handlebars'
],
exports: 'Thorax'
}
},
paths: {
jquery: 'lib/jquery/jquery.min',
underscore: '../../../../assets/lodash.min',
backbone: 'lib/backbone/backbone',
handlebars: '../../../../assets/handlebars.min',
thorax: 'lib/thorax',
text: 'lib/require/text'
}
});
require([
'views/app',
'routers/todomvc'
], function( AppView, TodoMVCRouter ) {
// Initialize routing and start Backbone.history()
new TodoMVCRouter();
Backbone.history.start();
// Initialize the application view
var view = new AppView();
$('body').append(view.el);
});
define([
'underscore',
'backbone',
'common'
], function( _, Backbone, Common ) {
return Backbone.Model.extend({
// Default attributes for the todo
// and ensure that each todo created has `title` and `completed` keys.
defaults: {
title: '',
completed: false
},
// Toggle the `completed` state of this todo item.
toggle: function() {
this.save({
completed: !this.get('completed')
});
},
isVisible: function () {
var isCompleted = this.get('completed');
if (Common.TodoFilter === '') {
return true;
} else if (Common.TodoFilter === 'completed') {
return isCompleted;
} else if (Common.TodoFilter === 'active') {
return !isCompleted;
}
}
});
});
define([
'jquery',
'backbone',
'thorax',
'collections/todos',
'common'
], function( $, Backbone, Thorax, Todos, Common ) {
return Thorax.Router.extend({
routes: {
"": "setFilter",
":filter": "setFilter"
},
setFilter: function( param ) {
// Set the current filter to be used
Common.TodoFilter = param ? param.trim().replace(/^\//, '') : '';
// Thorax listens for a `filter` event which will
// force the collection to re-filter
Todos.trigger('filter');
}
});
});
<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>
<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}}
define([
'jquery',
'underscore',
'backbone',
'thorax',
'collections/todos',
'views/todo-item',
'views/stats',
'text!templates/app.handlebars',
'common'
], function( $, _, Backbone, Thorax, Todos, TodoItemView, StatsView, appTemplate, Common ) {
// TodoView & StatsView needs to be included in "define"
// as it will be initialized by this view
return Thorax.View.extend({
// In a require.js application the name is primarily for
// consistency and debugging purposes
name: 'app',
template: Handlebars.compile(appTemplate),
// Delegated events for creating new items, and clearing completed ones.
events: {
'keypress #new-todo': 'createOnEnter',
'click #toggle-all': 'toggleAllComplete',
// The collection helper in the template will bind the collection
// to the view. Any events in this hash will be bound to the
// collection.
collection: {
all: '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 = Todos;
this.todosCollection.fetch();
this.render();
},
toggleToggleAllButton: function() {
this.$('#toggle-all').attr('checked', !this.todosCollection.remaining().length);
},
// This function is specified in the collection helper as the filter
// and will be called each time a model changes, or for each item
// when the collection is rendered
filterTodoItem: function(model) {
return model.isVisible();
},
// Generate the attributes for a new Todo item.
newAttributes: function() {
return {
title: this.$('#new-todo').val().trim(),
order: Todos.nextOrder(),
completed: false
};
},
// If you hit return in the main input field, create new **Todo** model,
// persisting it to *localStorage*.
createOnEnter: function( e ) {
if ( e.which !== Common.ENTER_KEY || !this.$('#new-todo').val().trim() ) {
return;
}
Todos.create( this.newAttributes() );
this.$('#new-todo').val('');
},
toggleAllComplete: function() {
var completed = this.$('#toggle-all')[0].checked;
Todos.each(function( todo ) {
todo.save({
'completed': completed
});
});
}
});
});
define([
'jquery',
'underscore',
'backbone',
'thorax',
'collections/todos',
'text!templates/stats.handlebars',
'common'
], function( $, _, Backbone, Thorax, Todos, statsTemplate, Common ) {
return Thorax.View.extend({
name: 'stats',
template: Handlebars.compile(statsTemplate),
events: {
'click #clear-completed': 'clearCompleted',
// The "rendered" event is triggered by Thorax each time render()
// is called and the result of the template has been appended
// to the View's $el
rendered: 'highlightFilter'
},
initialize: function() {
// Whenever the Todos collection changes re-render the stats
// render() needs to be called with no arguments, otherwise calling
// it with arguments will insert the arguments as content
Todos.on('all', _.debounce(function() {
this.render();
}), this);
},
// Clear all completed todo items, destroying their models.
clearCompleted: function() {
_.each( Todos.completed(), function( todo ) {
todo.destroy();
});
return false;
},
// Each time the stats view is rendered this function will
// be called to generate the context / scope that the template
// will be called with. "context" defaults to "return this"
context: function() {
var remaining = Todos.remaining().length;
return {
itemText: remaining === 1 ? 'item' : 'items',
completed: Todos.completed().length,
remaining: remaining
};
},
// Highlight which filter will appear to be active
highlightFilter: function() {
this.$('#filters li a')
.removeClass('selected')
.filter('[href="#/' + ( Common.TodoFilter || '' ) + '"]')
.addClass('selected');
}
});
});
\ No newline at end of file
define([
'jquery',
'underscore',
'backbone',
'thorax',
'common'
], function( $, _, Backbone, Common ) {
return Thorax.View.extend({
//... is a list tag.
tagName: 'li',
// Cache the template function for a single item.
name: 'todo-item',
// The DOM events specific to an item.
events: {
'click .toggle': 'toggleCompleted',
'dblclick label': 'edit',
'click .destroy': 'clear',
'keypress .edit': 'updateOnEnter',
'blur .edit': 'close',
// The "rendered" event is triggered by Thorax each time render()
// is called and the result of the template has been appended
// to the View's $el
rendered: function() {
this.$el.toggleClass( 'completed', this.model.get('completed') );
}
},
// Toggle the `"completed"` state of the model.
toggleCompleted: function() {
this.model.toggle();
},
// Switch this view into `"editing"` mode, displaying the input field.
edit: function() {
this.$el.addClass('editing');
this.$('.edit').focus();
},
// Close the `"editing"` mode, saving changes to the todo.
close: function() {
var value = this.$('.edit').val().trim();
if ( value ) {
this.model.save({ title: value });
} else {
this.clear();
}
this.$el.removeClass('editing');
},
// If you hit `enter`, we're through editing the item.
updateOnEnter: function( e ) {
if ( e.which === Common.ENTER_KEY ) {
this.close();
}
},
// Remove the item, destroy the model from *localStorage* and delete its view.
clear: function() {
this.model.destroy();
}
});
});
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