Sebastian Hoitz Blog Archive

Spine.JS and Chicago Boss

28 January 2012

This is a follow up to Chicago Boss and Backbone.js.

We use Chicago Boss as an MVC framework and SpineJS on the client side to bind the models to the DOM. We think this is a really great setup and are building our future projects with this architecture.

In this blog post we want to show you how to get up and running with CB and Spine!

Set up a new Chicago Boss project

First of all, make sure you have Chicago Boss installed on your computer. Then create a new CB app:

make app PROJECT="todomanager"

It should now set up a skeleton project under ../todomanager.

This is what my output looks like:

==> ChicagoBoss-0.6.10 (create)
Writing ../todomanager/start-dev.sh
Writing ../todomanager/start.sh
Writing ../todomanager/start-server.bat
Writing ../todomanager/Makefile
Writing ../todomanager/todomanager.app.src
Writing ../todomanager/boss.config
Writing ../todomanager/src/mail/todomanager_outgoing_mail_controller.erl
Writing ../todomanager/src/mail/todomanager_incoming_mail_controller.erl
Writing ../todomanager/priv/init/todomanager_01_news.erl
Writing ../todomanager/priv/static/chicago-boss.png
Writing ../todomanager/priv/static/favicon.ico
Writing ../todomanager/priv/todomanager.routes

Install Spine

Cool. Now we are ready to start setting up spine. The easiest way to get Spine is to use npm (Node Package Manager). If you don’t have npm yet, you can get it with this sweet one-liner:

curl http://npmjs.org/install.sh | sh

Once we have that, we should install spine.app. It takes care for us of setting up all the required skeletons for our JS app. We can also already install hem, we will need that later:

npm install -g hem spine.app

After npm has done its magic, set up a spine project. Execute this in the todomanager folder:

spine app frontend
cd frontend
npm install

This creates a lot more files and installs all npm dependencies. But bear with me.

We will use Hem to bundle and package all these files created by Spine. Hem is a pretty neat little packaging tool written by Alex MacCaw, the creator of Spine. What it does is bundle all your Javascript and CSS resources and puts them into one single JS and CSS file. Pretty neat, huh?

When you look at the files created, you should see a file called slug.json. Open it.

This is the configuration file for hem. We now need to change some settings, so that spine plays along nicely with our Chicago Boss setup.

It should look like this:

{
  "dependencies": [
    "es5-shimify",
    "json2ify",
    "jqueryify",
    "spine",
    "spine/lib/local",
    "spine/lib/ajax",
    "spine/lib/route",
    "spine/lib/tmpl",
    "spine/lib/manager"
  ],
  "libs": []
}

Change the file, so that it looks like this:

    {
      "dependencies": [
        "es5-shimify",
        "json2ify",
        "jqueryify",
        "spine",
        "spine/lib/local",
        "spine/lib/ajax",
        "spine/lib/route",
        "spine/lib/tmpl",
        "spine/lib/manager"
      ],
      "libs": [],
      "public": "../priv/static"
    }

This tells hem to put the generated javascript and css file into the publicly accessible folder of Chicago Boss. Give it a try by running

hem build

You should see those files being added at the right location.

Let’s start building something!

Let’s see what we want to achieve:

We will start by setting up the Chicago Boss model.

Chicago Boss

Create a file src/model/todo.erl:

-module(todo, [Id, Subject, Done]).
-compile(export_all).

That’s our model. Chicago Boss takes care of all the remaining magical functions like persisting etc. Cool, huh?

Now we create the todo REST resource.

Create the controller src/controller/todomanager_todo_controller.erl:

-module(todomanager_todo_controller, [Req]).
-compile(export_all).
-default_action(index).

%%
%% List
%%
%% GET todo/index
%%
index('GET', []) ->
  Todos = boss_db:find(todo, []),
  case Todos of
    [] ->
      {output, <<"[]">>, [{"Content-Type", "application/json"}]};
    _Else ->
      {json, Todos}
  end;

%%
%% Read
%%
%% GET todo/index/todo-1
%%
index('GET', [Id]) ->
  Todo = boss_db:find(Id),
  % TODO for some reason, when we have a non-existent todo, we still output the json
  % data and don't jump to the not_found section.
  case Todo of
    Todo ->
      {json, [{todo, Todo}]};
    [] ->
      not_found
  end;

%%
%% Create
%%
%% POST todo/index
%%
index('POST', []) ->
  Body = element(2, mochijson:decode(Req:request_body())),
  io:format("~p", [proplists:get_value("subject", Body)]),
  Todo = todo:new(id, proplists:get_value("subject", Body), false),
  %io:format("~p", [Todo]),
  {json, [{todo, element(2, Todo:save())}]};

%%
%% Update
%%
%% PUT todo/index/123
%%
index('PUT', [Id]) ->
  Todo = boss_db:find(Id),
  Body = element(2, mochijson:decode(Req:request_body())),
  %% Set the new values
  NewTodo = Todo:attributes([
      {subject, proplists:get_value("subject", Body)},
      {done, proplists:get_value("done", Body)}
    ]),
  {json, [{todo, element(2, NewTodo:save())}]}.

This creates all our required end points for our REST controller.

However, we also need an index controller in Chicago Boss to deliver our required initial HTML, so that our client-side Spine app can actually start working.

So create this index controller in src/controller/todomanager_index_controller.erl:

-module(todomanager_index_controller, [Req]).
-compile(export_all).

index('GET', []) ->
  {ok, []}.

We also need the view script. It is placed in src/view/index/index.html:

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="/static/application.css">
    <title>My sweet Erlang Todo app</title>
  </head>
  <body>
    <div id="app">
      <h1>Todos:</h1>
      <form action="/todo/create" method="post">
        <input type="text" name="subject" placeholder="Enter your todo item">
        <input type="submit" value="Save">
      </form>
      <ul class="items">
      </ul>
      <footer>
        <span class="countVal">0</span> todos left.
      </footer>
    </div>
    <script type="text/javascript" src="/static/application.js"></script>

    <script type="text/javascript">
      $ = require("jqueryify");
      App = require("index");

      $(function() {
        new App({el: $("#app")});
      });

    </script>
  </body>
</html>

This will deliver our initial HTML, include the files generated by hem and bootstrap our app.

Cool! By now we are pretty much done with our Erlang setup.

So now we can start writing our Spine app.

Spine

Spine apps are built in a pretty similar fashion. You have models, views and controllers.

So let’s start by writing our client-side model. We will place this file in frontend/app/models/todo.coffee:

Spine = require("spine")

class Todo extends Spine.Model
  @configure "Todo", "id", "subject", "done"

  @extend Spine.Model.Ajax
  @url: "/todo/index"

  @active: ->
    @select (item) -> !item.done

  @done: ->
    @select (item) -> !!item.done

module.exports = Todo

By calling

@extend Spine.Model.Ajax
@url: "/todo/index"

we tell Spine to persist via AJAX to a REST resource located at “/todo/index”.

Let’s create our todo controller /frontend/app/controllers/todo.coffee

In Spine it is best practice when you create a controller for each “widget” that we have on our side.

When looking at our todo app, we have two “widgets”. One to contain the list of todos, and then one widget per each todo, that takes care of all the events for this single todo.

So we have to create two controllers in our todo controller file. First we have to import some dependencies:

Spine = require("spine")
$ = Spine.$
Model = require("models/todo")

This is our TodoItem widget controller:

class TodoItem extends Spine.Controller
  events:
    "change input[type=checkbox]": "toggle"
    "dblclick": "edit"
    "blur input[type=text]": "close"
    "keypress input[type=text]": "blurOnEnter"

  elements:
    "input[type=text]": "input"

  constructor: ->
    super
    @item.bind("update", @render)
    @item.bind("destroy", @remove)

  render: =>
    @replace $(require("views/todo/todo")(@item))
    @

  toggle: ->
    @item.done = !@item.done
    @item.save()

  edit: ->
    @el.addClass("editing")
    @input.focus()

  blurOnEnter: (e) ->
    if e.keyCode is 13 then e.target.blur()

  close: ->
    @el.removeClass("editing")
    @item.updateAttributes({subject: @input.val()})

  remove: =>
    @el.remove()

Let’s look at some interesting parts here:

events:
  "change input[type=checkbox]": "toggle"
  "dblclick": "edit"
  "blur input[type=text]": "close"
  "keypress input[type=text]": "blurOnEnter"

This binds dom events to certain actions in our controller. Spine takes care for us of attaching these events to our dom.

render: =>
  @replace $(require("views/todo/todo")(@item))
  @

This renders our todo view and passes it our todo item as parameter so we can display some of its data.

class Todo extends Spine.Controller
  events:
    "submit form": "create"
    "click .clear": "clear"

  elements:
    ".items": "items"
    "form input": "input"
    ".countVal": "count"

  constructor: ->
    super

    @log "Initialited"

    Model.bind "create", @addOne
    Model.bind "refresh", @addAll
    Model.bind "refresh change", @renderCount
    Model.fetch()

  # Add a single todo item
  addOne: (todo) =>
    view = new TodoItem(item: todo)

    @items.append view.render().el

  # After a refresh
  addAll: =>
    Model.each @addOne

  # Create a new todo
  create: (e) ->
    e.preventDefault()
    @log @input.val()
    Model.create(subject: @input.val())
    @input.val ""

  renderCount: =>
    active = Model.active().length
    @count.text(active)

module.exports = Todo

This is the remaining controller that takes care of managing all our TodoItem instances.

We also need our todo view script /frontend/app/views/todo/todo.eco:

<li id="<%= @id %>">
  <input type="checkbox" name="todo[]" value="<%= @id %>"
    id="checkbox-<%= @id %>" <% if @done: %>checked="checked"<% end %>>
  <label for="checkbox-<%= @id %>"><%= @subject %></label>
  <div class="edit">
    <input type="text" value="<%= @subject %>">
  </div>
</li>

Remaining bootstrap code /frontend/app/index.coffee:

require('lib/setup')

Spine = require('spine')
Todo = require("controllers/todo")

module.exports = Todo

Some basic CSS /frontend/css/index.styl:

@import './mixin'

body, html
  font-family:Georgia
  font-size:25px
  background:#f0f0f0
  margin:0
  padding:0

#app
  margin:50px auto
  padding:2em
  width:500px
  background:#fff
  border-radius:5px
  box-shadow:0px 0px 10px rgba(0,0,0,0.4)

  h1
    text-align:center

  input[type=text]
    font-size:25px
    width:500px
    padding:8px 0

  input[type=submit]
    display:none

  ul
    list-style:none
    padding-left:0

  ul li
    padding:10px 0
    border-bottom:solid 1px #ddd

  ul li input
    position:relative
    top:-4px

  label:hover
    cursor:pointer

  li .edit
    display:none

  li:hover
    background:lightyellow

  li.editing .edit
    display:block

  li.editing label,
  li.editing input[type=checkbox]
    display:none

And we are ready to give it a test run!

Execute hem watch in our frontend folder, and ./start-dev.sh in the todomanager root dir.

Then open a browser and navigate to “localhost:8001” and you should see our todo app. You can add entries, double click existing entries to edit them and mark todos as done.

You can find the existing source code on Github.

I hope this was helpful for you! I appreciate any feedback :-)

blog comments powered by Disqus
Fork me on GitHub