Commit a3c6bafd authored by Harry Brundage's avatar Harry Brundage Committed by Sindre Sorhus

Close GH-149: Updated Batman example. Fixes #14

parent 6f3db0f0
# Batman TodoMVC
A todo app built using [Batman](http://batmanjs.org), inspired by [TodoMVC](https://github.com/addyosmani/todomvc).
## Running it
Spin up an HTTP server and visit http://localhost/labs/architecture-examples/batman/index.html
## Persistence
A quick note: This app uses `Batman.LocalStorage` to persist the Todo records across page reloads. Batman's `localStorage` engine sticks each record under it's own key in `localStorage`, which is a departure from the TodoMVC application specification, which asks that all the records are stored under one key as a big blob. Batman stores records this way so that the whole set doesn't need to be parsed just to find one record or check if that record exists.
## Building it
This app is written in CoffeeScript, so to make changes, please edit `js/app.coffee` and rebuild the JavaScript with the `coffee` compiler.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Batman • TodoMVC</title>
<link rel="stylesheet" href="../../../assets/base.css">
</head>
<body>
<div data-yield="main"></div>
<div data-defineview="todos/all">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<form data-formfor-todo="newTodo" data-event-submit="createTodo">
<input id="new-todo" type="text" placeholder="What needs to be completed?" autofocus data-bind="todo.title">
</form>
</header>
<section id="main" data-showif="Todo.all.length">
<input id="toggle-all" type="checkbox" data-event-change="toggleAll" data-source="Todo.completed.length | equals Todo.all.length">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<li data-foreach-todo="currentTodos"
data-addclass-completed="todo.completed"
data-addclass-editing="todo.editing" >
<div class="view" data-hideif="todo.editing" data-event-doubleclick="toggleEditing">
<input class="toggle" type="checkbox" data-bind="todo.completed" data-event-change="todoDoneChanged">
<label data-bind="todo.title"></label>
<button class="destroy" data-event-click="destroyTodo"></button>
</div>
<input class="edit" type="text"
data-bind="todo.title"
data-bind-id="'todo-input-' | append todo.id"
data-event-blur="toggleEditing"
data-event-keypress="disableEditingUponSubmit" >
</li>
</ul>
</section>
<footer id="footer" data-showif="Todo.all.length">
<span id="todo-count">
<strong data-bind="Todo.active.length"></strong>
<span data-bind="'item' | pluralize Todo.active.length, false"></span>
left
</span>
<ul id="filters">
<li>
<a data-addclass-selected="currentRoute.action | equals 'all'" data-route="'/'">All</a>
</li>
<li>
<a data-addclass-selected="currentRoute.action | equals 'active'" data-route="'/active'">Active</a>
</li>
<li>
<a data-addclass-selected="currentRoute.action | equals 'completed'" data-route="'/completed'">Completed</a>
</li>
</ul>
<button id="clear-completed" data-event-click="clearCompleted" data-showif="Todo.completed.length">Clear completed (<span data-bind="Todo.completed.length"></span>)</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
<p>Created by <a href="http://batmanjs.org">Harry Brundage</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</div>
<script src="../../../assets/base.js"></script>
<script src="js/es5-shim.js"></script>
<script src="js/batman.js"></script>
<script src="js/app.js"></script>
</body>
</html>
class Alfred extends Batman.App
@root 'todos#all'
@route "/completed", "todos#completed"
@route "/active", "todos#active"
class Alfred.TodosController extends Batman.Controller
constructor: ->
super
@set 'newTodo', new Alfred.Todo(completed: false)
all: ->
@set 'currentTodos', Alfred.Todo.get('all')
completed: ->
@set 'currentTodos', Alfred.Todo.get('completed')
@render source: 'todos/all'
active: ->
@set 'currentTodos', Alfred.Todo.get('active')
@render source: 'todos/all'
createTodo: ->
@get('newTodo').save (err, todo) =>
if err
throw err unless err instanceof Batman.ErrorsSet
else
@set 'newTodo', new Alfred.Todo(completed: false, title: "")
todoDoneChanged: (node, event, context) ->
todo = context.get('todo')
todo.save (err) ->
throw err if err && !err instanceof Batman.ErrorsSet
destroyTodo: (node, event, context) ->
todo = context.get('todo')
todo.destroy (err) -> throw err if err
toggleAll: (node, context) ->
Alfred.Todo.get('all').forEach (todo) ->
todo.set('completed', !!node.checked)
todo.save (err) ->
throw err if err && !err instanceof Batman.ErrorsSet
clearCompleted: ->
Alfred.Todo.get('completed').forEach (todo) ->
todo.destroy (err) -> throw err if err
toggleEditing: (node, event, context) ->
todo = context.get('todo')
editing = todo.set('editing', !todo.get('editing'))
if editing
input = document.getElementById("todo-input-#{todo.get('id')}")
input.focus()
input.select()
else
if todo.get('title')?.length > 0
todo.save (err, todo) ->
throw err if err && !err instanceof Batman.ErrorsSet
else
todo.destroy (err, todo) ->
throw err if err
disableEditingUponSubmit: (node, event, context) ->
if Batman.DOM.events.isEnter(event)
@toggleEditing(node, event, context)
class Alfred.Todo extends Batman.Model
@encode 'title', 'completed'
@persist Batman.LocalStorage
@validate 'title', presence: true
@storageKey: 'todos-batman'
@classAccessor 'active', ->
@get('all').filter (todo) -> !todo.get('completed')
@classAccessor 'completed', ->
@get('all').filter (todo) -> todo.get('completed')
@wrapAccessor 'title', (core) ->
set: (key, value) -> core.set.call(@, key, value?.trim())
window.Alfred = Alfred
Alfred.run()
// Generated by CoffeeScript 1.3.1
(function() {
var Alfred,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
Alfred = (function(_super) {
__extends(Alfred, _super);
Alfred.name = 'Alfred';
function Alfred() {
return Alfred.__super__.constructor.apply(this, arguments);
}
Alfred.root('todos#all');
Alfred.route("/completed", "todos#completed");
Alfred.route("/active", "todos#active");
return Alfred;
})(Batman.App);
Alfred.TodosController = (function(_super) {
__extends(TodosController, _super);
TodosController.name = 'TodosController';
function TodosController() {
TodosController.__super__.constructor.apply(this, arguments);
this.set('newTodo', new Alfred.Todo({
completed: false
}));
}
TodosController.prototype.all = function() {
return this.set('currentTodos', Alfred.Todo.get('all'));
};
TodosController.prototype.completed = function() {
this.set('currentTodos', Alfred.Todo.get('completed'));
return this.render({
source: 'todos/all'
});
};
TodosController.prototype.active = function() {
this.set('currentTodos', Alfred.Todo.get('active'));
return this.render({
source: 'todos/all'
});
};
TodosController.prototype.createTodo = function() {
var _this = this;
return this.get('newTodo').save(function(err, todo) {
if (err) {
if (!(err instanceof Batman.ErrorsSet)) {
throw err;
}
} else {
return _this.set('newTodo', new Alfred.Todo({
completed: false,
title: ""
}));
}
});
};
TodosController.prototype.todoDoneChanged = function(node, event, context) {
var todo;
todo = context.get('todo');
return todo.save(function(err) {
if (err && !err instanceof Batman.ErrorsSet) {
throw err;
}
});
};
TodosController.prototype.destroyTodo = function(node, event, context) {
var todo;
todo = context.get('todo');
return todo.destroy(function(err) {
if (err) {
throw err;
}
});
};
TodosController.prototype.toggleAll = function(node, context) {
return Alfred.Todo.get('all').forEach(function(todo) {
todo.set('completed', !!node.checked);
return todo.save(function(err) {
if (err && !err instanceof Batman.ErrorsSet) {
throw err;
}
});
});
};
TodosController.prototype.clearCompleted = function() {
return Alfred.Todo.get('completed').forEach(function(todo) {
return todo.destroy(function(err) {
if (err) {
throw err;
}
});
});
};
TodosController.prototype.toggleEditing = function(node, event, context) {
var editing, input, todo, _ref;
todo = context.get('todo');
editing = todo.set('editing', !todo.get('editing'));
if (editing) {
input = document.getElementById("todo-input-" + (todo.get('id')));
input.focus();
return input.select();
} else {
if (((_ref = todo.get('title')) != null ? _ref.length : void 0) > 0) {
return todo.save(function(err, todo) {
if (err && !err instanceof Batman.ErrorsSet) {
throw err;
}
});
} else {
return todo.destroy(function(err, todo) {
if (err) {
throw err;
}
});
}
}
};
TodosController.prototype.disableEditingUponSubmit = function(node, event, context) {
if (Batman.DOM.events.isEnter(event)) {
return this.toggleEditing(node, event, context);
}
};
return TodosController;
})(Batman.Controller);
Alfred.Todo = (function(_super) {
__extends(Todo, _super);
Todo.name = 'Todo';
function Todo() {
return Todo.__super__.constructor.apply(this, arguments);
}
Todo.encode('title', 'completed');
Todo.persist(Batman.LocalStorage);
Todo.validate('title', {
presence: true
});
Todo.storageKey = 'todos-batman';
Todo.classAccessor('active', function() {
return this.get('all').filter(function(todo) {
return !todo.get('completed');
});
});
Todo.classAccessor('completed', function() {
return this.get('all').filter(function(todo) {
return todo.get('completed');
});
});
Todo.wrapAccessor('title', function(core) {
return {
set: function(key, value) {
return core.set.call(this, key, value != null ? value.trim() : void 0);
}
};
});
return Todo;
})(Batman.Model);
window.Alfred = Alfred;
Alfred.run();
}).call(this);
This diff is collapsed.
# batman.js • [TodoMVC](http://todomvc.com)
Batman.js is a framework for building rich web applications with CoffeeScript or JavaScript. App code is concise and declarative, thanks to a powerful system of view bindings and observable properties. The API is designed with developer and designer happiness as its first priority.
[Issue](https://github.com/addyosmani/todomvc/issues/14)
\ No newline at end of file
html, body {
margin: 0;
padding: 0;
}
body {
font-family: "Helvetica Neue", helvetica, arial, sans-serif;
font-size: 14px;
line-height: 1.4em;
background: #eeeeee;
color: #333333;
}
#views {
width: 520px;
margin: 0 auto 40px auto;
background: white;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0;
-moz-border-radius: 0 0 5px 5px;
-o-border-radius: 0 0 5px 5px;
-webkit-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
#tasks {
padding: 20px;
}
#tasks h1 {
font-size: 36px;
font-weight: bold;
text-align: center;
padding: 0 0 10px 0;
}
#tasks input[type="text"] {
width: 466px;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
padding: 6px;
border: 1px solid #999999;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset;
}
#tasks input::-webkit-input-placeholder {
font-style: italic;
}
#tasks .items {
margin: 10px 0;
list-style: none;
}
#tasks .item {
padding: 15px 20px 15px 0;
position: relative;
font-size: 24px;
border-bottom: 1px solid #cccccc;
}
#tasks .item.done span {
color: #777777;
text-decoration: line-through;
}
#tasks .item .destroy {
position: absolute;
right: 5px;
top: 14px;
display: none;
cursor: pointer;
width: 20px;
height: 20px;
background: url(../images/destroy.png) no-repeat 0 0;
}
#tasks .item:hover .destroy {
display: block;
}
#tasks li:hover .todo-destroy {
display: block;
}
#tasks .destroy:hover {
background-position: 0 -20px;
}
/*
#tasks .item .edit { display: none; }
#tasks .item.editing .edit { display: block; }
#tasks .item.editing .view { display: none; }
*/
#tasks footer {
display: block;
margin: 20px -20px -20px -20px;
overflow: hidden;
color: #555555;
background: #f4fce8;
border-top: 1px solid #ededed;
padding: 0 20px;
line-height: 36px;
-moz-border-radius: 0 0 5px 5px;
-o-border-radius: 0 0 5px 5px;
-webkit-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
#tasks .clear {
display: block;
float: right;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
color: #555555;
font-size: 11px;
margin-top: 8px;
padding: 0 10px 1px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
-o-border-radius: 12px;
border-radius: 12px;
-moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0;
cursor: pointer;
}
#tasks .clear:hover {
background: rgba(0, 0, 0, 0.15);
-moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
-o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0;
}
#tasks .clear:active {
position: relative;
top: 1px;
}
#tasks .count span {
font-weight: bold;
}
#credits {
width: 520px;
margin: 30px auto;
color:
#999;
text-shadow:
rgba(255, 255, 255, .8) 0 1px 0;
text-align: center;
}
#credits a {
color: #888;
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<title>Todos</title>
<link rel="stylesheet" href="css/todo.css" type="text/css">
<script src="lib/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="lib/batman.js" type="text/javascript"></script>
<script src="lib/batman.jquery.js" type="text/javascript"></script>
<script src="lib/es5-shim.js" type="text/javascript"></script>
<script src="lib/coffee-script.js" type="text/javascript"></script>
</head>
<body>
<div id="views">
<div id="tasks">
<h1>Todos</h1>
<form data-formfor-todo="controllers.todos.emptyTodo" data-event-submit="controllers.todos.create">
<input type="text" placeholder="What needs to be done?" data-bind="todo.body" />
</form>
<div class="items">
<div class="item" data-foreach-todo="Todo.all" data-addclass-done="todo.completed">
<div class="view" title="Double click to edit..." data-event-doubleclick="todo.on_edit" data-hideif="todo.editing">
<input type="checkbox" data-bind="todo.completed" data-event-change="todo.save" />
<span data-bind="todo.body"></span>
<a class="destroy" data-event-click="todo.destroy"></a>
</div>
<div data-showif="todo.editing">
<form data-event-submit="todo.on_blur">
<input type="text" data-bind="todo.body" data-event-blur="todo.on_blur"/>
</form>
</div>
</div>
</div>
<footer>
<a class="clear" data-event-click="Todo.clear">Clear completed</a>
<div class="count"><span class="countVal" data-bind="Todo.remaining"></span> left</div>
</footer>
<!--
This version by
Currently missing todo edit.
-->
</div>
</div>
<div id="credits">
<p>Created by <a href="https://github.com/johnknott/batman-todos-example">John Knott</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</div>
<script type="text/coffeescript">
class Example extends Batman.App
@root 'todos#index'
class Example.Todo extends Batman.Model
@global yes
@persist Batman.LocalStorage
@encode 'body', 'completed'
completed: false
editing: false
on_edit: (node, event) ->
@set 'todo.editing', true
$(node).parent().find("input[type=text]").focus()
on_blur: (node, event) ->
@set 'todo.editing', false
@todo.save()
@clear: (node, event) ->
Todo.all.forEach (todo) ->
if todo.get 'completed'
todo.destroy()
@classAccessor 'remaining',
get: -> Todo.get('all').get('length')
class Example.TodosController extends Batman.Controller
emptyTodo: null
index: ->
@set 'emptyTodo', new Todo
@render false
create: =>
@emptyTodo.save =>
@set 'emptyTodo', new Todo
Example.run()
</script>
</body>
</html>
(function() {
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Batman.Request.prototype.send = function(data) {
var options, _ref;
options = {
url: this.get('url'),
type: this.get('method'),
dataType: this.get('type'),
data: data || this.get('data'),
username: this.get('username'),
password: this.get('password'),
beforeSend: __bind(function() {
return this.fire('loading');
}, this),
success: __bind(function(response, textStatus, xhr) {
this.set('status', xhr.status);
this.set('response', response);
return this.fire('success', response);
}, this),
error: __bind(function(xhr, status, error) {
this.set('status', xhr.status);
this.set('response', xhr.responseText);
xhr.request = this;
return this.fire('error', xhr);
}, this),
complete: __bind(function() {
return this.fire('loaded');
}, this)
};
if ((_ref = this.get('method')) === 'PUT' || _ref === 'POST') {
if (!this.get('formData')) {
options.contentType = this.get('contentType');
} else {
options.contentType = false;
options.processData = false;
options.data = this.constructor.objectToFormData(options.data);
}
}
return jQuery.ajax(options);
};
Batman.mixins.animation = {
show: function(addToParent) {
var jq, show, _ref, _ref2;
jq = $(this);
show = function() {
return jq.show(600);
};
if (addToParent) {
if ((_ref = addToParent.append) != null) {
_ref.appendChild(this);
}
if ((_ref2 = addToParent.before) != null) {
_ref2.parentNode.insertBefore(this, addToParent.before);
}
jq.hide();
setTimeout(show, 0);
} else {
show();
}
return this;
},
hide: function(removeFromParent) {
$(this).hide(600, __bind(function() {
var _ref;
if (removeFromParent) {
return (_ref = this.parentNode) != null ? _ref.removeChild(this) : void 0;
}
}, this));
return this;
}
};
}).call(this);
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
<a href="http://todomvc.meteor.com" data-source="http://meteor.com" data-content="Meteor is an ultra-simple environment for building modern websites.A Meteor application is a mix of JavaScript that runs inside a client web browser, JavaScript that runs on the Meteor server inside a Node.js container, and all the supporting HTML fragments, CSS rules, and static assets. Meteor automates the packaging and transmission of these different components. And, it is quite flexible about how you choose to structure those components in your file tree.">Meteor</a> <a href="http://todomvc.meteor.com" data-source="http://meteor.com" data-content="Meteor is an ultra-simple environment for building modern websites.A Meteor application is a mix of JavaScript that runs inside a client web browser, JavaScript that runs on the Meteor server inside a Node.js container, and all the supporting HTML fragments, CSS rules, and static assets. Meteor automates the packaging and transmission of these different components. And, it is quite flexible about how you choose to structure those components in your file tree.">Meteor</a>
</li> </li>
<li> <li>
<a href="architecture-examples/batmanjs/index.html" data-source="http://batmanjs.org/" data-content="Batman.js is a framework for building rich web applications with CoffeeScript or JavaScript. App code is concise and declarative, thanks to a powerful system of view bindings and observable properties. The API is designed with developer and designer happiness as its first priority.">Batman.js</a> <a href="architecture-examples/batman/index.html" data-source="http://batmanjs.org/" data-content="Batman.js is a framework for building rich web applications with CoffeeScript or JavaScript. App code is concise and declarative, thanks to a powerful system of view bindings and observable properties. The API is designed with developer and designer happiness as its first priority.">Batman.js</a>
</li> </li>
<li> <li>
<a href="architecture-examples/gwt/index.html" data-source="https://developers.google.com/web-toolkit/" data-content="Google Web Toolkit (GWT) is an MVP development toolkit for building and optimizing complex browser-based applications. GWT is used by many products at Google, including Google AdWords.">Google Web Toolkit</a> <a href="architecture-examples/gwt/index.html" data-source="https://developers.google.com/web-toolkit/" data-content="Google Web Toolkit (GWT) is an MVP development toolkit for building and optimizing complex browser-based applications. GWT is used by many products at Google, including Google AdWords.">Google Web Toolkit</a>
...@@ -170,4 +170,4 @@ ...@@ -170,4 +170,4 @@
<script src="../site/js/bootstrap.min.js"></script> <script src="../site/js/bootstrap.min.js"></script>
<script src="../site/js/main.js"></script> <script src="../site/js/main.js"></script>
</body> </body>
</html> </html>
\ No newline at end of file
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