Eco (Embedded CoffeeScript Templates) Helpers

by Chris Smith (@quartzmo)

A preview of ScriptyBooks: Backbone + CoffeeScript

Introduction

Developers with a Ruby on Rails background have asked me about view helper functions in Eco. Unlike EJS, which provides some Rails-like helpers, Eco mostly leaves it to you to supply your own. The flexible nature of JavaScript makes it simple to do so, as we shall see in the following CoffeeScript examples.

Beginning Context: Just an Object Literal

At its most basic, the context provided to Eco is an object composed of literal values, likely parsed from JSON. Eco makes your context argument available as this (aliased to @ in CoffeeScript).

eco-helpers-literal
context = name: 'Fred'
template = 'Hi there, <%= @name %>.'
alert eco.render(template, context)

You can run and edit the code in this tutorial. (Yes, the code listing is an embedded CoffeeScript editor. Yes, I know it’s not that obvious.)

Intermediate Context: Introducing a Function

A helper is really nothing more than a function added to the context object.

eco-helpers-function
context =
  name: 'Fred'
  loud: (s) -> s.toUpperCase()

template = 'Hi there, <%= @loud @name %>.'

alert eco.render(template, context)

Let’s try enclosing our toUpperCase() call in some conditional logic. We can also add a bit more structure to the model portion of our context, by nesting our model attributes under a person property. For example:

eco-helpers-logic
context =
  person:
    name: 'Fred'
    friend: true
  nameHelper: (person) -> if person.friend then @person.name.toUpperCase() else "bud"

template = 'Hi there, <%= @nameHelper @person %>.'

alert eco.render(template, context)

This is a good time to get your hands dirty. Go ahead, experiment with the code above and run your changes. To restore the original listing, simply refresh the page.

Advanced Context: Organizing Helpers and Data

In this final version, we use jQuery’s extend function to merge helpers and model into a single context. The model data is likely to differ each time we call render, but the helpers should remain static.

Of course, we aren’t going to write all of our helpers ourselves. Why reinvent the wheel when the JavaScript community has created so many helpful libraries? For example, we wouldn’t want to revisit the mundane, generic task of formatting currency, when we can use accounting.js instead. (It’s already available as a global in this page. The topic of dependency management with modules will wait for a more advanced lesson.) Using jQuery’s extend function, we can easily copy all the properties of window.accounting into our context.

eco-helpers-extend
data =
  person:
    name: 'Fred'
    # more attributes . . .
  debt: 15000

context = $.extend({}, window.accounting, data)

template = 'Hi there, <%= @person.name %>.\nI know, I still owe you <%= @formatMoney @debt %>!'

alert eco.render(template, context)

A more advanced approach to dependency management would employ modules to manage scope, but for simple scenarios, assembling a collection of helpers from globally accessible functions is sufficient.

Built-in Helpers

Finally, it’s not entirely true that Eco does not provide any helpers. It provides two. Click the run button to the right of the code listing to see what they are.

eco-helpers-builtin
template = 'I am Eco. My built-in helpers are <%= @safe (x for x of @).join(" & ") %>.'
alert eco.render(template)

By default, Eco escapes the characters <,>,&, and " in its output, replacing them with the correct HTML entities. The safe helper lets you tell Eco not to escape those characters in the string that you pass to it. The escape helper lets you indicate parts you do want escaped within the content you mark as safe. Remove @safe from the example above to see the escaped output.

Exercise: Using Underscore.string

A great set of helper functions can be found in the Underscore.string library, which can be combined with CoffeeScript comprehensions for some powerful transformations. Avoiding a fixed-length solution, write a helper that produces “the Good, the Bad and the Ugly” from the array ["good", "bad", "ugly"]. (Underscore.string functions are loaded by default at _.str, for example: _.str.capitalize('fred').)

eco-helpers-exercise
context =
  words: ["good", "bad", "ugly"]
  capitalize: _.str.capitalize          # hint
  toSentence: _.str.toSentence  # hint
  movieTitleize: (array) ->
    array.toString() # Fail! Replace with mucho mas awesome.

template = 'Here come <%= @movieTitleize @words %>.'

alert eco.render(template, context)

Solution

@toSentence("the " + @capitalize(s) for s in array)

Conclusion

Eco does not presume to be a one-stop shop for your views, instead choosing to do one thing well. The roster of String-related libraries in JavaScript is enormous and quickly evolving, making Eco’s narrow focus on the core rendering of CoffeeScript fortuitous. We will take a look at how several of CoffeeScript’s features significantly improve the readability of view templates in the next section.

A preview of ScriptyBooks: Backbone + CoffeeScript

Copyright 2012 Chris Smith