Views written in Coffee-Script 08 Mar 2012

Using an unobtrusive syntax in combination with an expressive language allows the development of Domain specific languages (DSL) for specialized problems.

On problem that can be solved with a DSL is creating templates.

A lot of different template engines are available. While these template engines mature more and more language concepts are added to satisfy the users. And a lot of template engines try to recreate the languages they are created in. An great example of this is smarty which tries to replicate features of php. PHP is a document oriented programming languages (the code is embedded into a regular html document), which can be seen as a template engine, too. ;)

This tutorial motivates the subject of writing template code in the currently used programming language itself.

Coffee-Script, by chance, has a Libraries that allow to develop views in the same language.

The obvious advantages of this are:

As the template code is written in the applications language of choice the developers have to know one language less. As views are code the whole discipline of software engineering can be applied to the view development, too.

Aim of this tutorial

Available libraries

I started developing with drykup. I have used this library in a few projects and had no complaints that far. I recommend to use this library for starters. As the code of the library is pretty awful, I implemented the same library myself. The result is plainkup, this library should be working, too. But plainkup is not field tested, yet.

Usage

To create a view one has to create an instance of the builder The builder has all available html elements (builder.a, builder.div, …). Executing a method of the builder appends it to the internal model. The internal model can be retrieved with builder.htmlOut

builder.a href: '/login', 'Go to Login'
// appends <a href="/login">Go to Login</a> to `builder`

The first parameter is an object with all attributes of the element

builder.div a: 'x', b: 'y', 'text'
// output: <div a="x" b="x">text</div>

The last parameter is either a text which is filled between the tags. If the last parameter is a function the function is evaluated within the context of the tag.

builder.div ->
    builder.p 'foo'
    builder.p 'bar'
// <div><p>foo</p><p>bar</p></div>

Nesting is unlimited

div -> div -> div -> div 'hey'
// <div><div><div><div>hey</div></div></div></div>

The above gives us the technical knowledge of how to write view code in coffee-script. Now we need to think of a way of structuring our view code. Our aim is to reduce code duplication and make software engineering a bit more pleasant.

View Interface

All views (partial or not) should have the same signature:

greeting = (builder, params) ->
    name = params.name
    {div, p} = builder
    div ->
        p "hello #{name}"
        p "how are you?"

The function greeting receives two parameters: An initialized builder (builder = drykup()) and an object which contains the parameters.

We inject the builder in each view, because this gives us nesting for free:

# params.names = ['foo', 'bar', 'baz']

greetPeople = (builder, params) ->
    _.each params.names, (name) -> greeting builder, name: name
# hello foo, hello bar, hello baz..

I structured my views directory in the following way

/view/:controller/:action.coffee

For example:

/view/user/all.coffee # view for the list of all users
/view/user/edit.coffee # view for editing a user
/view/user/foo.coffee # random partial which is used by the `user` views

All action have the same signature (builder, params).

Adding a Layout

Most of the time web applications have a default layout.

A Layout is the view code that is wrapped around each page

This layout takes a menu and a view and creates a complete view.

This is an example of a default layout:

layout = (view, menu) -> (builder, params) ->
    {head, script, ...} = builde
    head ->
        script ...
        ...
    body ->
        div class: container', ->
            #initializing the injected `menu` in the default layout
            menu builder, params
            # add a flash message
            alert builder, params.flash
            # print the default page
            view builder params

The layout has the same signature as all views and partials.

Wrapping it together

The most convenient way of accessing the views is the following

params = getAllRequiredParamsForTheView()
result = view.user.edit params
# result is a string containing the complete rendered view

If we implement this interface, all implementation details are hidden behind the interface and the view code does only depend on the submitted parameters. This allows us to implement a clean and decoupled view interface.

As all views have the same interface and we have the layout, we just need to wrap everything together:

view-api.coffee:

view = require './view' // all views
layout = require './layout' // the default layout signature:


addLayout = (view) -> (params) ->
    builder = drykup()
    layout(view, menu) builder, params
    builder.htmlOut

module.exports =
    login: addLayout view.login
    logout: addLayout view.logout
    user:
        all: addLayout api.user.all
    ...