The personal weblog of Martin Donath, professional web developer, technology ninja and design enthusiast based in Cologne, Germany.

Awesome Client-side MVC With Coffeescript and Composer.js

Developing modern and complex Web 2.0 applications, one is always faced with the problem of structuring and dividing the frontend Javascript code in a way that is maintainable, reusable and easy to be extended. Frameworks like jQuery and MooTools battle cross-browser compatibility issues but don’t provide any high-level structuring mechanisms. Meanwhile, most applications use the Model-View-Control (MVC) pattern in their backends, so why not also use it in the frontend?

At the time of writing, there is quite a range of open source Javascript MVC frameworks, including Backbone, SproutCore and Spine just to name a few. Unfortunately, almost all of them are tightly coupled with jQuery. Since I’m a big fan of the strictly object-oriented approach of MooTools, I searched the web for an alternative and found a new and promising MooTools-based framework: Composer.js.

Adding Mooml as a client-side templating engine and Coffeescript for syntactic sugar, I will discuss my current approach of building complex and maintainable web frontends. This article assumes that you are familiar with the basic concepts of Javascript, have written some code in MooTools and Coffeescript and are used to the MVC pattern.

A Brief Introduction

MooTools (over jQuery)

In my opinion, MooTools has taken just the right path encapsulating and abstracting altering operations on the DOM with support across all major browsers. I prefer its object-oriented approach over jQuery’s function-centric approach. If you’re interested, I recommend reading jQuery vs MooTools for a side-by-side comparison.

Before the advent of Coffeescript, MooTools was also one of very few Javascript frameworks to provide an elegant abstraction of the prototypical inheritance model of Javascript by introducing classes. However, since Coffeescript’s class model is agnostic when it comes to frameworks and even nicer for reasons of convenience and readability, we will stick with Coffeescript when it comes to classes.

Coffeescript

Let’s face it – Javascript is by far not the most elegant programming language in the world, but since it’s the only native language to be interpreted by web browsers, there’s no choice but to use it.

Coffeescript, which compiles into Javascript, comes to the rescue by adding syntactic sugar and embracing the good parts of Javascript. It introduces a very convenient way of handling objects and classes in Javascript by encapsulating its prototypical inheritence model. With coffeescript you can easily create classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal
  constructor: (name) ->
    @name = name

class Cat extends Animal
  constructor: (name, @favfood) ->
    super name

  meow: ->
    "Meow! I'm #{@name} and i want #{@favfood}!"

cat = new Cat 'Zelda', 'Brekkies'
alert cat.meow()

Compiled into Javascript the source then looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(function() {
  var Animal, Cat, cat,
    __hasProp = Object.prototype.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;
    };

  Animal = (function() {
    function Animal(name) {
      this.name = name;
    }
    return Animal;
  })();

  Cat = (function(_super) {
    __extends(Cat, _super);
    function Cat(name, favfood) {
      this.favfood = favfood;
      Cat.__super__.constructor.call(this, name);
    }
    Cat.prototype.meow = function() {
      return "Meow! I'm " + this.name + " and i want " + this.favfood + "!";
    };
    return Cat;
  })(Animal);

  cat = new Cat('Zelda', 'Brekkies');
  alert(cat.meow());

}).call(this);

Now that’s a real saving – (including blank lines) we have 13 lines of Coffeescript vs. 36 lines of Javascript! Also, the Coffeescript code is far more readable as it eliminates a huge amount of Javascript’s ugliness. Really cool.

Since this is not intended to be an introduction to Coffeescript, knowledge of the basic syntax of Coffeescript and its inherent features is recommended for the rest of this article. If you aren’t familiar with it yet, I absolutely recommend reading The Little Book on Coffeescript. It’s free!

Composer.js

Composer.js is a new MVC framework building on the shoulders of the MooTools core. It borrows features from Backbone and Spine and combines them in a very lightweight approach, making absolutely no assumptions about the communication of the frontend and backend of your application. This means you have to implement the data-syncing yourself, but as we will see, this is not a big deal. Composer.js consists of:

  • Models: Models are simply objects that encapsulate the data of an entity, so just like models in a classical MVC way. They can be synced with the server, included in collections and handled by controllers.
  • Collections: Collections are just sets of models. Like models, they can be synced with the server. A model can be included in several collections, but the collection priority dictates which collection is consulted when syncing the model with the server.
  • Controllers: Controllers are the glue between models, collections and the DOM. Every controller needs to be tied to an element in the DOM. Further elements which are children of the main element can also be referenced within the controller.

No views, huh? Actually, this is also a cool thing about Composer.js – it makes no assumptions about the data-syncing and it also leaves the choice of a templating engine up to you. Every controller provides a render method, so this is where the view invocation should be situated. And as denoted in the abstract, we will use …

Mooml

Mooml, which is short for MooTools Markup Language, is a MooTools port of the jQuery-based Jaml templating engine. It eliminates the need of the write-intensive creation of new elements with new Element. Furthermore it simplifies the process of injecting new elements in the DOM by providing easy mechanisms for nesting.

Using Coffeescript, Mooml almost feels like it’s server-side companion Haml. Additionally, the code is slimmer as we can almost solely rely on the indentation when nesting elements. For example, consider creating a simple table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Render items list table with header and row section.
Mooml.register 'items-list', (result) ->
  table { 'class': 'items-list' },
    caption result.caption
    thead {}, Mooml.render 'items-list-header', result.header
    tbody {}, Mooml.render 'items-list-row', result.items

# Render items list header section.
Mooml.register 'items-list-header', (item) ->
  tr {},
    th value for value in item

# Render items list row section.
Mooml.register 'items-list-row', (item) ->
  tr {},
    td value for value in item

The templates have been registered in the Mooml namespace, so when you now want to render the template from a controller with an arbitrary result object, you just have to invoke Mooml.render with a template name and an object:

1
2
3
4
5
6
7
result =
  caption: 'The best cat treats'
  header:  [['Name', 'Brand', 'Price']]
  items:   [[...], [...], ...]

table = Mooml.render 'items-list', result
table.inject 'contents'

Resulting in the following (valid X)HTML code to be injected:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<table class="items-list">
  <caption>The best cat treats</caption>
  <thead>
    <tr>
      <th>Name</th>
      <th>Brand</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Duck</td>
      <td>Whiskas</td>
      <td>3.45</td>
    </tr>
    <tr>
      <td>Chicken</td>
      <td>Brekkies</td>
      <td>2.75</td>
    </tr>
  </tbody>
</table>

Isn’t that just nice? We can now easily create tables from anywhere we want with only two lines of code and almost arbitrary result objects. If we extend our result objects to include options, we can pass them in the template e.g. for setting CSS classes.

Building a Simple MVC Skeleton

After this short introduction, we will build a simple skeleton for a frontend MVC application. Using Coffeescript we setup the necessary bootstrapping, write models and collections and create controllers as well as views.

Bootstraping

Before we can begin using Composer.js with Coffeescript, we have to do a little bit of bootstrapping. I suggest the following directory structure, which is perfect if you use Ruby on Rails as a backend, because from Rails 3.1 on, you can use the asset pipeline for automatic compilation and compression:

1
2
3
4
5
6
7
8
9
/javascripts
. /application
. . /collections    (4)
. . /controllers    (5)
. . /helpers        (2)
. . /models         (3)
. . /views          (6)
. . router.coffee   (7)
. bootstrap.coffee  (1)

This should also work with other backends, but you have to make sure that the Coffeescript code is compiled into Javascript and the files are included in the order defined by the number located after the respective file or folder. If you’re using Rails it is recommended to use the application.js situtated in assets/javascripts as a manifest file. You can find mine here. Note that Mooml, Composer.js and MooTools are located in vendor/assets/javascripts.

The necessary compatibility code for bootstrapping Composer.js together with Coffeescript can be found in the file bootstrap.coffee. Furthermore, the file contains a reference implementation of the Composer.sync method, which assumes your backend to behave in a RESTful way. This is necessary in order to make the CRUD (Create Read Update Delete) operations work, as Composer.js uses the CRUD paradigm to sync its data with the server.

Bootstrapping logic (bootstrap.coffee) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# In order to use Composer.js together with CoffeeScript, we have to
# reference the extend-functions and define them in the global scope.
# This ensures an easy implementation.
#
# Now, we can use CoffeeScript's default class syntax:
#   class MyModel extends Application.Model ...
@Application =
  Collection: Composer.Collection.extend()
  Controller: Composer.Controller.extend()
  Model:      Composer.Model.extend()

# Our custom namespace - call it as you like.
@Custom = {}

# Syncs a model or collection with the server.
#
# This is the central method through which we communicate with the server.
# All objects (either models or collections) call this method in order to
# stay in sync with the server.
Composer.sync = (method, object, options) ->

  # CRUD to HTTP mapping.
  http =
    create: 'post'
    read:   'get'
    update: 'put'
    delete: 'delete'

  # If an object needs to be created or updated, assemble the data. The
  # attributes contained in the 'data' member are written in a namespace
  # defined by the member 'data_key', if set.
  attr = {}
  if method in ['create', 'update']

    # Extract raw data and remove id.
    temp = object.toJSON()
    delete temp[object.id_key]

    # If the member 'data_key' is set, write into separate namespace.
    if object.data_key? then attr[object.data_key] = temp else attr = temp

  # Build the request and send it.
  new Request.JSON

    # Initialize the request.
    method:     http[method]
    url:        object.get_url()
    data:       attr

    # Callbacks for successful and failed requests.
    onSuccess:  options.success
    onError:    options.error

  # After all this configuration, finally send the request.
  .send()

For example, if you have a model called User, Composer.js assumes that it is modelled as a server-side resource and all CRUD actions live in /users/.

Models and Collections

Let’s assume we have a list of users which we want to display. In our world, every user has an id, a name, a certain age and an email-address. Since users are considered entities, we write a simple model for them. Note that the model is registered in a global namespace which was defined in the bootstrap.coffee. Implementation:

User model (user.coffee) download
1
2
3
4
5
6
# Simple user model.
class Custom.User extends Application.Model

  # The user introduces himself.
  introduce: ->
    "Hello, my name is #{@name} and I'm #{@age}!"

Initializing a new user, you can pass all attributes to the constructor - Composer.js does the rest for you. No explicit fields must be defined. However, if you need to process the data passed to the constructor before it is assigned to the model, you should overwrite the parse method. See the documentation for more information.

Now we add a simple collection in application/collections:

User collection (users.coffee) download
1
2
3
4
5
6
# Simple user collection.
class Custom.Users extends Application.Collection

  # Corresponding model and additional configuration.
  model: Custom.User
  url:   '/users'

When you now invoke fetch on a new collection, the collection invokes Composer.sync to fetch the correponding models from the server with GET /users/. The models are then initialized with the data returned and appended to the collection.

Controllers and Views

Next we can write the necessary controller code and implement the views. Controllers are the glue between models, collections and the DOM. Tied to a DOM element, they can register as event listeners on specific events. Those events can also be triggered on child-nodes. Let’s assume we have a section referenced by the ID #contents within which we want to render a user table with the data obtained by our user collection. When the child node referenced by #button is clicked, an alert box should appear with the result of the introduction method of the user model:

User controller (user.coffee) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# Simple user controller.
class Custom.UserController extends Application.Controller

  # Main element tied to the controller.
  el: '#contents'

  # Relevant child nodes.
  elements:
    '#button': 'button'

  # Event-to-callback mapping.
  events:
    'mouseenter tr': 'toggle'
    'mouseleave tr': 'toggle'
    'click #button': 'introduce'

  # Initialize the table by fetching the users from the server and
  # invoking the render method.
  init: ->
    @users = new Custom.Users()
    @users.fetch()
    @render()

  # Introduce all users currently tied to the controller included in
  # the user collection.
  introduce: ->
    @users.each (user) ->
      alert user.introduce()

  # Toggle the state of a row by toggling a CSS class. The actual row
  # is passed as a second parameter.
  toggle: (event, target) ->
    target.toggleClass 'active'

  # Render user table and inject it into the main element referenced
  # by the controller. For each user all data is mapped to an array.
  render: (users) ->
    table = Mooml.render 'buttoned-items-list',
      caption: 'Simple user list'
      header:  ['Id', 'Name', 'Age', 'Email']
      results: @users.map (user) ->
        Object.values Object.subset user.toJSON(),
          ['id', 'name', 'age', 'email']
    @el.adopt table
    @refresh_elements()

Since we’re adding new elements to the DOM for which we are registering event listeners, we have to refresh the element references after the injection.

The only thing missing is the view. I suggest to locate them in the application/views folder in thematically grouped files. We already described a view called items-list which we will use to render a buttoned-items-list view.

List views (lists.coffee) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Render items list table with header and row section.
Mooml.register 'items-list', (result) ->
  table { 'class': 'items-list' },
    caption result.caption
    thead {}, Mooml.render 'items-list-header', result.header
    tbody {}, Mooml.render 'items-list-row', result.items

# Render items list header section.
Mooml.register 'items-list-header', (item) ->
  tr {},
    th value for value in item

# Render items list row section.
Mooml.register 'items-list-row', (item) ->
  tr {},
    td value for value in item

# Simple items list with button.
Mooml.register 'buttoned-items-list', (result) ->
  [ Mooml.render('items-list', result),
    button { id: 'button' } ]

And that is basically it. Isn’t that just awesome? The resulting Javascript code is cleanly divided in functionality: models and collections do the data-syncing and -handling, controllers handle DOM elements and events, process data and render views, and views can be arbitrarily split into partials for better nesting and reusability.

The only drawback might be some (mostly neglectable) performance loss, since the creation and handling of objects isn’t as cheap as setting a HTML string via innerHTML. However, this is an inherent problem of all abstractions.

Conclusion

This is the current approach I am undertaking to build maintainable and complex web frontends. I hope you find it helpful, at least to know how to couple Composer.js and Coffeescript, since this was a point that took me some time.

Mooml is a nice templating engine but others can be used, since Composer.js is agnostic when it comes to the templating engine and data-syncing procedure. MVC doesn’t have backend in its name, so the next time you build a web frontend from the ground up - think in MVC. You’ll love it.

You can download the whole source code here.

Comments