Commit aa503579 authored by Sindre Sorhus's avatar Sindre Sorhus

Backbone.Marionette cleanup

- Convert to tabs
- Trim trailing whitespace
parent 9b81a25c
# Backbone.Marionette TodoMVC app
Backbone.Marionette is a composite application library for Backbone.js that aims to simplify the construction of large scale JavaScript applications. It is a collection of common design and implementation patterns found in the applications that Derick Bailey has been building with Backbone, and includes various pieces inspired by composite application architectures, such as Microsoft's "Prism" framework.
This implementation of the application uses Marionette's module system. Variations using RequireJS and a more classic approach to JavaScript modules are also [available](https://github.com/marionettejs/backbone.marionette/wiki/Projects-and-websites-using-marionette).
\ No newline at end of file
This implementation of the application uses Marionette's module system. Variations using RequireJS and a more classic approach to JavaScript modules are also [available](https://github.com/marionettejs/backbone.marionette/wiki/Projects-and-websites-using-marionette).
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Marionette • TodoMVC</title>
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="css/app.css">
<!--[if IE]>
<script src="../../../assets/ie.js"></script>
<![endif]-->
<script type="text/html" id="template-footer">
<span id="todo-count"><strong></strong> items left</span>
<ul id="filters">
<li>
<a href="#">All</a>
</li>
<li>
<a href="#active">Active</a>
</li>
<li>
<a href="#completed">Completed</a>
</li>
</ul>
<button id="clear-completed">Clear completed</button>
</script>
<script type="text/html" id="template-header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
</script>
<script type="text/html" id="template-todoItemView">
<div class="view">
<input class="toggle" type="checkbox" <% if (completed) { %>checked<% } %>>
<label><%= title %></label>
<button class="destroy"></button>
</div>
<input class="edit" value="<%= title %>">
</script>
<script type="text/html" id="template-todoListCompositeView">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul>
</script>
</head>
<body>
<section id="todoapp">
<header id="header"></header>
<section id="main"></section>
<footer id="footer"></footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>
Created by <a href="http://github.com/jsoverson">Jarrod Overson</a>
and <a href="http://github.com/derickbailey">Derick Bailey</a>,
using <a href="http://marionettejs.com">Backbone.Marionette</a>
</p>
<p>Further variations on the Backbone.Marionette app are also <a href="https://github.com/marionettejs/backbone.marionette/wiki/Projects-and-websites-using-marionette">available</a>.</p>
</footer>
<!-- vendor libraries -->
<script src="../../../assets/base.js"></script>
<script src="../../../assets/jquery.min.js"></script>
<script src="../../../assets/lodash.min.js"></script>
<script src="js/lib/backbone.js"></script>
<script src="js/lib/backbone-localStorage.js"></script>
<script src="js/lib/backbone.marionette.js"></script>
<!-- application -->
<script src="js/TodoMVC.js"></script>
<script src="js/TodoMVC.Todos.js"></script>
<script src="js/TodoMVC.Layout.js"></script>
<script src="js/TodoMVC.TodoList.Views.js"></script>
<script src="js/TodoMVC.TodoList.js"></script>
<script>
$(function(){
// Start the TodoMVC app (defined in js/TodoMVC.js)
TodoMVC.start();
});
</script>
</body>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Marionette • TodoMVC</title>
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="css/app.css">
<!--[if IE]>
<script src="../../../assets/ie.js"></script>
<![endif]-->
<script type="text/html" id="template-footer">
<span id="todo-count"><strong></strong> items left</span>
<ul id="filters">
<li>
<a href="#">All</a>
</li>
<li>
<a href="#active">Active</a>
</li>
<li>
<a href="#completed">Completed</a>
</li>
</ul>
<button id="clear-completed">Clear completed</button>
</script>
<script type="text/html" id="template-header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
</script>
<script type="text/html" id="template-todoItemView">
<div class="view">
<input class="toggle" type="checkbox" <% if (completed) { %>checked<% } %>>
<label><%= title %></label>
<button class="destroy"></button>
</div>
<input class="edit" value="<%= title %>">
</script>
<script type="text/html" id="template-todoListCompositeView">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul>
</script>
</head>
<body>
<section id="todoapp">
<header id="header"></header>
<section id="main"></section>
<footer id="footer"></footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>
Created by <a href="http://github.com/jsoverson">Jarrod Overson</a>
and <a href="http://github.com/derickbailey">Derick Bailey</a>
using <a href="http://marionettejs.com">Backbone.Marionette</a>
</p>
<p>Further variations on the Backbone.Marionette app are also <a href="https://github.com/marionettejs/backbone.marionette/wiki/Projects-and-websites-using-marionette">available</a>.</p>
</footer>
<!-- vendor libraries -->
<script src="../../../assets/base.js"></script>
<script src="../../../assets/jquery.min.js"></script>
<script src="../../../assets/lodash.min.js"></script>
<script src="js/lib/backbone.js"></script>
<script src="js/lib/backbone-localStorage.js"></script>
<script src="js/lib/backbone.marionette.js"></script>
<!-- application -->
<script src="js/TodoMVC.js"></script>
<script src="js/TodoMVC.Todos.js"></script>
<script src="js/TodoMVC.Layout.js"></script>
<script src="js/TodoMVC.TodoList.Views.js"></script>
<script src="js/TodoMVC.TodoList.js"></script>
<script>
$(function(){
// Start the TodoMVC app (defined in js/TodoMVC.js)
TodoMVC.start();
});
</script>
</body>
</html>
TodoMVC.module("Layout", function(Layout, App, Backbone, Marionette, $, _){
// Layout Header View
// ------------------
Layout.Header = Backbone.Marionette.ItemView.extend({
template : "#template-header",
// UI bindings create cached attributes that
// point to jQuery selected objects
ui : {
input : '#new-todo'
},
events : {
'keypress #new-todo': 'onInputKeypress'
},
onInputKeypress : function(evt) {
var ENTER_KEY = 13;
var todoText = this.ui.input.val().trim();
if ( evt.which === ENTER_KEY && todoText ) {
this.collection.create({
title : todoText
});
this.ui.input.val('');
}
}
});
// Layout Footer View
// ------------------
Layout.Footer = Backbone.Marionette.Layout.extend({
template : "#template-footer",
// UI bindings create cached attributes that
// point to jQuery selected objects
ui : {
count : '#todo-count strong',
filters : '#filters a'
},
events : {
'click #clear-completed' : 'onClearClick'
},
initialize : function() {
this.bindTo(App.vent, 'todoList:filter', this.updateFilterSelection, this);
this.bindTo(this.collection, 'all', this.updateCount, this);
},
onRender : function() {
this.updateCount();
},
updateCount : function() {
var count = this.collection.getActive().length;
this.ui.count.html(count);
if (count === 0) {
this.$el.parent().hide();
} else {
this.$el.parent().show();
}
},
updateFilterSelection : function(filter) {
this.ui.filters
.removeClass('selected')
.filter('[href="#' + filter + '"]')
.addClass('selected');
},
onClearClick : function() {
var completed = this.collection.getCompleted();
completed.forEach(function destroy(todo) {
todo.destroy();
});
}
});
// Layout Header View
// ------------------
Layout.Header = Backbone.Marionette.ItemView.extend({
template : "#template-header",
// UI bindings create cached attributes that
// point to jQuery selected objects
ui : {
input : '#new-todo'
},
events : {
'keypress #new-todo': 'onInputKeypress'
},
onInputKeypress : function(evt) {
var ENTER_KEY = 13;
var todoText = this.ui.input.val().trim();
if ( evt.which === ENTER_KEY && todoText ) {
this.collection.create({
title : todoText
});
this.ui.input.val('');
}
}
});
// Layout Footer View
// ------------------
Layout.Footer = Backbone.Marionette.Layout.extend({
template : "#template-footer",
// UI bindings create cached attributes that
// point to jQuery selected objects
ui : {
count : '#todo-count strong',
filters : '#filters a'
},
events : {
'click #clear-completed' : 'onClearClick'
},
initialize : function() {
this.bindTo(App.vent, 'todoList:filter', this.updateFilterSelection, this);
this.bindTo(this.collection, 'all', this.updateCount, this);
},
onRender : function() {
this.updateCount();
},
updateCount : function() {
var count = this.collection.getActive().length;
this.ui.count.html(count);
if (count === 0) {
this.$el.parent().hide();
} else {
this.$el.parent().show();
}
},
updateFilterSelection : function(filter) {
this.ui.filters
.removeClass('selected')
.filter('[href="#' + filter + '"]')
.addClass('selected');
},
onClearClick : function() {
var completed = this.collection.getCompleted();
completed.forEach(function destroy(todo) {
todo.destroy();
});
}
});
});
TodoMVC.module("TodoList.Views", function(Views, App, Backbone, Marionette, $, _){
// Todo List Item View
// -------------------
//
// Display an individual todo item, and respond to changes
// that are made to the item, including marking completed.
Views.ItemView = Marionette.ItemView.extend({
tagName : 'li',
template : "#template-todoItemView",
ui : {
edit : '.edit'
},
events : {
'click .destroy' : 'destroy',
'dblclick label' : 'onEditClick',
'keypress .edit' : 'onEditKeypress',
'click .toggle' : 'toggle'
},
initialize : function() {
this.bindTo(this.model, 'change', this.render, this);
},
onRender : function() {
this.$el.removeClass('active completed');
if (this.model.get('completed')) this.$el.addClass('completed');
else this.$el.addClass('active');
},
destroy : function() {
this.model.destroy();
},
toggle : function() {
this.model.toggle().save();
},
onEditClick : function() {
this.$el.addClass('editing');
this.ui.edit.focus();
},
onEditKeypress : function(evt) {
var ENTER_KEY = 13;
var todoText = this.ui.edit.val().trim();
if ( evt.which === ENTER_KEY && todoText ) {
this.model.set('title', todoText).save();
this.$el.removeClass('editing');
}
}
});
// Item List View
// --------------
//
// Controls the rendering of the list of items, including the
// filtering of activs vs completed items for display.
Views.ListView = Backbone.Marionette.CompositeView.extend({
template : "#template-todoListCompositeView",
itemView : Views.ItemView,
itemViewContainer : '#todo-list',
ui : {
toggle : '#toggle-all'
},
events : {
'click #toggle-all' : 'onToggleAllClick'
},
initialize : function() {
this.bindTo(this.collection, 'all', this.update, this);
},
onRender : function() {
this.update();
},
update : function() {
function reduceCompleted(left, right) { return left && right.get('completed'); }
var allCompleted = this.collection.reduce(reduceCompleted,true);
this.ui.toggle.prop('checked', allCompleted);
if (this.collection.length === 0) {
this.$el.parent().hide();
} else {
this.$el.parent().show();
}
},
onToggleAllClick : function(evt) {
var isChecked = evt.currentTarget.checked;
this.collection.each(function(todo){
todo.save({'completed': isChecked});
});
}
});
// Application Event Handlers
// --------------------------
//
// Handler for filtering the list of items by showing and
// hiding through the use of various CSS classes
App.vent.on('todoList:filter',function(filter) {
filter = filter || 'all';
$('#todoapp').attr('class', 'filter-' + filter);
});
// Todo List Item View
// -------------------
//
// Display an individual todo item, and respond to changes
// that are made to the item, including marking completed.
Views.ItemView = Marionette.ItemView.extend({
tagName : 'li',
template : "#template-todoItemView",
ui : {
edit : '.edit'
},
events : {
'click .destroy' : 'destroy',
'dblclick label' : 'onEditClick',
'keypress .edit' : 'onEditKeypress',
'click .toggle' : 'toggle'
},
initialize : function() {
this.bindTo(this.model, 'change', this.render, this);
},
onRender : function() {
this.$el.removeClass('active completed');
if (this.model.get('completed')) this.$el.addClass('completed');
else this.$el.addClass('active');
},
destroy : function() {
this.model.destroy();
},
toggle : function() {
this.model.toggle().save();
},
onEditClick : function() {
this.$el.addClass('editing');
this.ui.edit.focus();
},
onEditKeypress : function(evt) {
var ENTER_KEY = 13;
var todoText = this.ui.edit.val().trim();
if ( evt.which === ENTER_KEY && todoText ) {
this.model.set('title', todoText).save();
this.$el.removeClass('editing');
}
}
});
// Item List View
// --------------
//
// Controls the rendering of the list of items, including the
// filtering of activs vs completed items for display.
Views.ListView = Backbone.Marionette.CompositeView.extend({
template : "#template-todoListCompositeView",
itemView : Views.ItemView,
itemViewContainer : '#todo-list',
ui : {
toggle : '#toggle-all'
},
events : {
'click #toggle-all' : 'onToggleAllClick'
},
initialize : function() {
this.bindTo(this.collection, 'all', this.update, this);
},
onRender : function() {
this.update();
},
update : function() {
function reduceCompleted(left, right) { return left && right.get('completed'); }
var allCompleted = this.collection.reduce(reduceCompleted,true);
this.ui.toggle.prop('checked', allCompleted);
if (this.collection.length === 0) {
this.$el.parent().hide();
} else {
this.$el.parent().show();
}
},
onToggleAllClick : function(evt) {
var isChecked = evt.currentTarget.checked;
this.collection.each(function(todo){
todo.save({'completed': isChecked});
});
}
});
// Application Event Handlers
// --------------------------
//
// Handler for filtering the list of items by showing and
// hiding through the use of various CSS classes
App.vent.on('todoList:filter',function(filter) {
filter = filter || 'all';
$('#todoapp').attr('class', 'filter-' + filter);
});
});
TodoMVC.module("TodoList", function(TodoList, App, Backbone, Marionette, $, _){
// TodoList Router
// ---------------
//
// Handle routes to show the active vs complete todo items
TodoList.Router = Marionette.AppRouter.extend({
appRoutes : {
"*filter": "filterItems"
}
});
// TodoList Controller (Mediator)
// ------------------------------
//
// Control the workflow and logic that exists at the application
// level, above the implementation detail of views and models
TodoList.Controller = function(){
this.todoList = new App.Todos.TodoList();
};
_.extend(TodoList.Controller.prototype, {
// Start the app by showing the appropriate views
// and fetching the list of todo items, if there are any
start: function(){
this.showHeader(this.todoList);
this.showFooter(this.todoList);
this.showTodoList(this.todoList);
this.todoList.fetch();
},
showHeader: function(todoList){
var header = new App.Layout.Header({
collection: todoList
});
App.header.show(header);
},
showFooter: function(todoList){
var footer = new App.Layout.Footer({
collection: todoList
});
App.footer.show(footer);
},
showTodoList: function(todoList){
App.main.show(new TodoList.Views.ListView({
collection : todoList
}));
},
// Set the filter to show complete or all items
filterItems: function(filter){
App.vent.trigger("todoList:filter", filter.trim() || "");
}
});
// TodoList Initializer
// --------------------
//
// Get the TodoList up and running by initializing the mediator
// when the the application is started, pulling in all of the
// existing Todo items and displaying them.
TodoList.addInitializer(function(){
var controller = new TodoList.Controller();
new TodoList.Router({
controller: controller
});
controller.start();
});
// TodoList Router
// ---------------
//
// Handle routes to show the active vs complete todo items
TodoList.Router = Marionette.AppRouter.extend({
appRoutes : {
"*filter": "filterItems"
}
});
// TodoList Controller (Mediator)
// ------------------------------
//
// Control the workflow and logic that exists at the application
// level, above the implementation detail of views and models
TodoList.Controller = function(){
this.todoList = new App.Todos.TodoList();
};
_.extend(TodoList.Controller.prototype, {
// Start the app by showing the appropriate views
// and fetching the list of todo items, if there are any
start: function(){
this.showHeader(this.todoList);
this.showFooter(this.todoList);
this.showTodoList(this.todoList);
this.todoList.fetch();
},
showHeader: function(todoList){
var header = new App.Layout.Header({
collection: todoList
});
App.header.show(header);
},
showFooter: function(todoList){
var footer = new App.Layout.Footer({
collection: todoList
});
App.footer.show(footer);
},
showTodoList: function(todoList){
App.main.show(new TodoList.Views.ListView({
collection : todoList
}));
},
// Set the filter to show complete or all items
filterItems: function(filter){
App.vent.trigger("todoList:filter", filter.trim() || "");
}
});
// TodoList Initializer
// --------------------
//
// Get the TodoList up and running by initializing the mediator
// when the the application is started, pulling in all of the
// existing Todo items and displaying them.
TodoList.addInitializer(function(){
var controller = new TodoList.Controller();
new TodoList.Router({
controller: controller
});
controller.start();
});
});
TodoMVC.module("Todos", function(Todos, App, Backbone, Marionette, $, _){
// Todo Model
// ----------
Todos.Todo = Backbone.Model.extend({
localStorage: new Backbone.LocalStorage('todos-backbone'),
defaults: {
title : '',
completed : false,
created : 0
},
initialize : function() {
if (this.isNew()) this.set('created', Date.now());
},
toggle : function() {
return this.set('completed', !this.isCompleted());
},
isCompleted: function() {
return this.get('completed');
}
});
// Todo Collection
// ---------------
Todos.TodoList = Backbone.Collection.extend({
model: Todos.Todo,
localStorage: new Backbone.LocalStorage('todos-backbone'),
getCompleted: function() {
return this.filter(this._isCompleted);
},
getActive: function() {
return this.reject(this._isCompleted);
},
comparator: function( todo ) {
return todo.get('created');
},
_isCompleted: function(todo){
return todo.isCompleted();
}
});
// Todo Model
// ----------
Todos.Todo = Backbone.Model.extend({
localStorage: new Backbone.LocalStorage('todos-backbone'),
defaults: {
title : '',
completed : false,
created : 0
},
initialize : function() {
if (this.isNew()) this.set('created', Date.now());
},
toggle : function() {
return this.set('completed', !this.isCompleted());
},
isCompleted: function() {
return this.get('completed');
}
});
// Todo Collection
// ---------------
Todos.TodoList = Backbone.Collection.extend({
model: Todos.Todo,
localStorage: new Backbone.LocalStorage('todos-backbone'),
getCompleted: function() {
return this.filter(this._isCompleted);
},
getActive: function() {
return this.reject(this._isCompleted);
},
comparator: function( todo ) {
return todo.get('created');
},
_isCompleted: function(todo){
return todo.isCompleted();
}
});
});
var TodoMVC = new Backbone.Marionette.Application();
TodoMVC.addRegions({
header : '#header',
main : '#main',
footer : '#footer'
header : '#header',
main : '#main',
footer : '#footer'
});
TodoMVC.on("initialize:after", function(){
Backbone.history.start();
Backbone.history.start();
});
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