Alfred - Batman's Todos

Because no single-page app framework is complete without the canonical todo list example, here it is in just a few trivial lines of code.

We'll setup our view in HTML, setup a model with inline CoffeeScript, and then bind everything together using batman.js.

Launch Alfred. The completed code can be found in the repo.

Layout

<!DOCTYPE html>
<html>
<head>
    <title>Alfred: Batman's Todos</title>

    <link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" href="resources/alfred.css" type="text/css">

    <script type="text/javascript" src="es5-shim.js"></script>
    <script type="text/javascript" src="lib/batman.js"></script>
</head>

<body>
    <div id="container">
        <h1>Alfred</h1>
        <form>
          <input class="new-item" placeholder="add a todo item" />
        </form>

        <ul id="items">
            <li>
                <input type="checkbox" />
                <label>title</label>
                <a>delete</a>
            </li>
            <li><span>number</span> <span>of items</span>
        </ul>
    </div>

    <script type="text/coffeescript">
    # Create our application.
    </script>
</body>
</html>
  

All the included resources may be found in the download above.

Application

First, we'll setup an instance of Batman.App. In addition to managing the our application lifecycle, the object acts as a namespace for all your other objects, like our Todo model.

# Create our application and namespace.
class Alfred extends Batman.App
  @global yes
  @root 'todos#index'


# Define the principle Todo model with `body` and `isDone` attributes, and tell it to persist itself using Local Storage.
class Alfred.Todo extends Batman.Model
  @global yes # global exposes this class on the global object, so you can access `Todo` directly.

  @persist Batman.LocalStorage
  @encode 'body', 'isDone'

  body: ''
  isDone: false


class Alfred.TodosController extends Batman.Controller
  index: ->
    # setup a binding for the new form
    @set 'emptyTodo', new Todo

    # add some example todos to show off.
    Todo.load (todos) ->
      if not todos.length
        new Todo(body: 'joker escaped arkham again', isDone: true).save()
        new Todo(body: 'riddler sent riemann hypothesis').save()
        new Todo(body: 'bane wants to meet, not worried').save()

    # prevent the implicit render of views/todos/index.html
    @render false

  create: =>
    @emptyTodo.save =>
      @set 'emptyTodo', new Todo


# Start the app
Alfred.run()
  

Bindings

Finally, we can now setup our HTML to bind to our working application. This makes everything come to live, with no additional code!

We start by iterating over Todo.all (a copy of this li will be made for each item, and whenever a new item is added). The iterated item will be available under the key we specify, in this case todo.

<ul id="items">
  <li data-foreach-todo="Todo.all">
  </li>
</ul>
  

Next, we can use data-bind to set the value of nodes within our iterator. Input nodes, like the checkbox, are smart enough to set the correct attribute.

<li data-foreach-todo="Todo.all">
    <input type="checkbox" data-bind="todo.isDone" />
    <label data-bind="todo.body"></label>
</li>
  

There are many other data-* attributes we can use to add complex behaviors. For example, here we'll add the class name done to our label when a certain binding evaluates to true.

<li data-foreach-todo="Todo.all">
    <input type="checkbox" data-bind="todo.isDone" />
    <label data-bind="todo.body" data-addclass-done="todo.isDone"></label>
</li>
  

Two new attributes. data-mixin will apply all of the properties from the given named bucket to this node, allowing for more complex behavior.

data-event-* allows us to easily add event handlers to a node. There are a few advantages over onclick: it will use addEventListener, let us access runtime methods, and we can support some more specialized events.

<li data-foreach-todo="Todo.all" data-mixin="animation">
    <input type="checkbox" data-bind="todo.isDone" />
    <label data-bind="todo.body" data-mixin="editable" data-addclass-done="todo.isDone"></label>
    <a data-event-click="todo.destroy">delete</a>
</li>
  

Bindings also support a Liquid-like filter syntax, allowing you to create more complex bindings without any additional presenter logic or code.

<ul id="items">
  <li data-foreach-todo="Todo.all">...</li>
  <li><span data-bind="Todo.all.length"></span> <span data-bind="'item' | pluralize Todo.all.length"></span></li>
</ul>
  

Finally, we can wire up our form to our controller in order to create new todos. The controllers object used here is an easy way to access the singleton instances of all controllers currently running in the app.

  <form data-formfor-todo="controllers.todos.emptyTodo" data-event-submit="controllers.todos.create">
    <input class="new-item" placeholder="add a todo item" data-bind="todo.body" />
  </form>
  

Fin

Launch Alfred. The completed code can be found in the repo.