Eco
(Embedded CoffeeScript Templates)

Chris Smith (@quartzmo)

Use arrow keys to navigate

A preview of ScriptyBooks: Backbone + CoffeeScript

Gateway Drug to JavaScript MVC

Our client-side code needs help.

We decide to bring in a lightweight JavaScript framework, probably Backbone.js.

Not this week.

But it's not all or nothing.

We can improve our client-side game in small steps.

And we can take our first small step without a framework. . .

Server & HTML, Old Cronies

We still favor placing logic for dynamic HTML on the server.

But as we move beyond page loads, it's making less and less sense.

"Cronyism is partiality to long-standing friends, especially by appointing them to positions of authority, regardless of their qualifications." -- Wikipedia

Too much AJAH

  1. A DOM event triggers an AJAX request for new data.
  2. The server fetches the data, then renders a fragment of HTML.
  3. The server returns the HTML fragment.
  4. On the client, the response handler attaches the HTML fragment to the DOM.

Wait, that X was supposed to be a J, not an H!

Why Should I Care?

An HTML endpoint is not an API, but a JSON endpoint might be.

APIs are sweet. They are the key to becoming a platform.

Platforms are very sweet.

Don’t like our UI? Go ahead, build your own.

Quickest and Dirtiest Template

The simplest form of embedded template is just a div hidden on the DOM:

<div id="person-template" style="display: none;">
  <div class="person">
    <p class="name"><b>Name:</b> </p>
    <p class="nickname"><b>Nickname:</b> </p>
    <p class="occupation"><b>Occupation:</b> </p>
    <p class="rank"><b>Rank:</b> </p>
  </div>
</div>

While there are drawbacks (more on those soon), this lets us render HTML.

Bring on the JSON:

{"name": "Erwin Rommel", "nickname": "Desert Fox", "occupation": {"name": "Soldier", "rank": "Field Marshal"}}

But This is Some Dirty Code

Here’s how an AJAX callback function using our hidden template might look.

(data) ->
  person = JSON.parse(data)
  el = $('#person-template div.person').clone()
  $('.name', el).append(person.name)
  $('.nickname', el).append(person.nickname)
  $('.occupation', el).append(person.occupation.name) if person.occupation
  if person.occupation.name is "Soldier"
    $('.rank', el).append(person.occupation.rank)
  else
    $('.rank', el).hide()
  $('ul#people').append(personEl)

Nesting much of this code in HTML would make things more intuitive.

Template Libraries, for Fun & Function

JavaScript CoffeeScript Summary
Underscore Eco Like ERB
EJS Like ERB + Rails (helpers and partials)
Jade CoffeeScript Jade Like Haml
Haml-js Haml Coffee Even more like Haml
mustache.js* Milk* Mustache (* Implementation language; Mustache is a “Logic-less” external DSL)
CoffeeKup A DSL for markup, in CoffeeScript

A Much Cleaner Callback

Here’s how an AJAX callback function using a library template might look.

(data) -> $('#people').append(JST['person'](JSON.parse(data)))

(The global JST object is provided by an asset packaging library.)

Underscore.js Templates

Underscore templates are almost identical to ERB, except that the code is JavaScript.

We've extracted our template to person.html.jst for packaging.

<div class="person">
  <p>Name: <%= person.name %></p>
  <p>Nickname: <%= person.nickname %></p>
  <p>Occupation: <%= person.occupation != null ? person.occupation.name : "" %></p>
  <% if (((occupation = person.occupation) != null ? occupation.name : void 0) === "Soldier") { %>
    <p>Rank: <%= person.occupation.rank %></p>
  <% } %>
</div>

But We <3 CoffeeScript!

Eco is very similar to Underscore templates and ERB, but with CoffeeScript.

One killer feature for templates is CoffeeScript's existential operator, a.k.a. the soak.

<div class="person">
  <p>Name: <%= @name %></p>
  <p>Nickname: <%= @nickname %></p>
  <p>Occupation: <%=  @occupation?.name %></p>
  <% if @occupation?.name is "Soldier": %>
     <p>Rank: <%= @occupation.rank %></p>
  <% end %>
</div>

Ending a line with a colon means everything until end will be indented.

Try Eco

Data

mountains: [
  {name: "Everest", stats: {meters: 8848}}
  {name: "Aconcagua"}
  {name: "Mount McKinley"}
  {name: "Kilimanjaro"}
  {name: "Elbrus"}
  {name: "Vinson Massif"}
  {name: "Carstensz Pyramid", stats: {meters: 4884}}
]

Result

 

restore

Helpers

In our example, the context we passed to Eco was just some re-hydrated JSON.

However, you can call any function you like. Just add it to the context.

formatMoney: window.accounting.formatMoney
cart:
  items: [
    {name: "Trucker Mug", price: 5.99 }
    {name: "Trucker Hat", price: 12.99 }
    {name: "550hp 18spd 2012 Peterbilt 389", price: 139500.0 }
  ]

It’s up to you to prepare your context, but once you do, help is at hand.

<span class="price"><%= @formatMoney item.price %></span>

Rails 3.1 Asset Pipeline

TL;DR

  1. gem 'eco'
  2. bundle install
  3. app/assets/javascripts/templates/person.jst.eco
  4. $('#people').append(JST['templates/person'](context))

Long version at my blog

For Rails applications without the asset pipeline, use Jammit for asset packaging.

Resources

Gmail, GitHub, Twitter: quartzmo