Commit a1081622 authored by Ryan Eastridge's avatar Ryan Eastridge

Remove thorax_require

parent 23457d50
Thorax + RequireJS TodoMVC
==========================
Unlike the vanilla Thorax and Thorax + Lumbar implementations, this example does not make use of the Thorax registry / named views and templates. The views are still assigned a name property for debugging and consistency (each view's element will be assigned a data-view-name HTML attribute), but each dependency is explicitly pulled in via `define` instead of being pulled in by the `view` or `template` helpers, or automatic assignment of templates to views when they share a name. For example in the require.js app:
# views/app.js
template: Handlebars.compile(appTemplate),
initialize: function() {
this.statsView = new StatsView;
}
# templates/app.handlebars
{{view statsView}}
In the Lumbar or vanilla Thorax implementations simply setting the name will auto assign the template of the same name, and the "stats" view can be included by name, rather than having to first initialize it.
# views/app.js
name: 'app'
# templates/app.handlebars
{{view "stats"}}
Thorax is flexible enough that the approach used in the require.js app will still work within lumbar or vanilla Thorax implementations, but the approach used in the require.js environment is the only one that will work with require.js
\ No newline at end of file
<!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});
// 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();
// Initialize the application view
var view = new AppView();
$('body').append(view.el);
Backbone.history.start();
});
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=todoItemView 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 statsView}}
{{/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 ) {
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.todoItemView = TodoItemView;
this.statsView = new StatsView;
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',
tagName: 'footer',
id: 'footer',
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 ) {
// This view has no template assigned in the class definition
// as it will recieve the capture block from the collection
// helper in templates/app.handlebars as it's template
return Thorax.View.extend({
name: 'todo-item',
//... is a list tag.
tagName: 'li',
// 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