Bujagali
A template system in JavaScript (kind of). Developed and currently in production at Rdio.
Overview
Bujagali is a flexible template system that is a thin layer on top of JavaScript which makes it easier to write HTML (or any templated text) using JavaScript. The syntax is modeled off of the Django templating system, but the behavior is substantially different.
Bujagali is really fast, really flexible, and really hard to use. It's written for people who know JavaScript well and is probably completely unusable by most designers.
The basic principle is to compile templates on the server-side to JS functions, then load them lazily on the client to render JSON data.
This system is designed to work in either server-side or client-side JavaScript
environments. It depends on the excellent underscore library, so you can
reference _
from anywhere in your templates.
There are 3 aspects to the system that require documentation.
- Writing templates
- Converting templates to JavaScript
- Rendering templates
Writing Templates
Every template is provided with a context variable, called ctx
, that contains
the object the template was rendered with.
###Special Tags
There are two special tags that must come at the top of your templates. These are used to reuse code found in other templates, and examples of their use is given in the 'Regular Tags' section below
#import <template path>
This ensures that a template is rendered before rendering your template. This is
used to make sure that templates containing macros (described below) have been
loaded and are those macros are available to use by the time your template is
rendered. Imports must be the first thing in a template.
#extends <template path>
This tag includes the template you specify in the current template. These must
occur above all other markup, but after #import
tags. This is used to provide
template inheritance, where the template being extended will later call into
the current template (see below for an example).
###Regular Tags
{{ <data> }}
Output whatever is returned, usually a variable or the result of some operation.
Example:
<h1>Name: {{ ctx.myName }}</h1>
<h2>Address: {{ self.formatAddress(ctx.myName) }}</h2>
<h3>Birth year: {{ 2010 - ctx.age }}</h3>
{% <javascript> %}
Execute the enclosed JavaScript, don't alter the markup in any way.
Example:
{% if (ctx.fruits && ctx.fruits.length) {
_.each(ctx.fruits, function(fruit) { %}
<li>{{ fruit }}</li>
{% });
} %}
{$ <block name> $}
This is one part of how inheritance is done. This tag will define a block for a subtemplate to override. This allows you to define where markup from a template that extends the current template will be placed. This will call a function of the name specified and output the result into the parent template.
Example:
Master template
<html>
<head>
<title>{{ ctx.title }}</title>
{$ scripts $}
{$ css $}
</head>
<body>
{$ content $}
</body>
</html>
Subtemplate
#extends /relative/path/to/master/template
{% function scripts() { %}
<script type="text/javascript" src="/myprogram.js"></script>
{% }
function css() { %}
<link type="text/css" rel="stylesheet" href="/mystyle.css" />
{% }
function content() { %}
Hello World!
{% } %}
{= <macro name>(<macro arguments>) <macro content> =}
This allows you to extend the template system and reuse pieces of templates in other places.
Example:
In some template:
{= render_address(address)
<div class="address">
<div class="street">{{ address.street }}</div>
{% if (address.aptNum) { %}
<div class="num">{{ address.aptNum }}</div>
{% } %}
<div class="line2">
{{ address.city }}, {{ address.state }} {{ address.zip }}
</div>
</div>
=}
Then in some other template:
#import /relative/path/to/first/template
<div class="address-list">
{% _.each(ctx.addresses, function(address) { %}
{{ self.render_address(address) }}
{% } %}
</div>
{# <comment> #}
Everything between these tags will be ignored and will not end up in the compiled template.
###Advanced Concepts
self
self
refers to the current instance of the template being rendered. You almost
always want to call macros and other functions provided by the templating system
with self
. For instance, escape
is a function available to all templates in
Bujagali.
<h1>Name: {{ self.escape(ctx.name) }}</h1>
Macros defined by other templates are also available through self (so long as
you #import
them first)
#import /myheadertemplate.html
<div class="header">{{ self.render_header(ctx) }}</div>
emit
emit
is a function that is available to all templates. It is what is called
by the moustache tag ({{ }}
). This can be useful for making some markup less
confusing.
Instead of
The car is{% if (ctx.sex == 'female') { %} hers.{% } else { %} his.{% } %}
You could do
The car is{% ctx.sex == 'female' ? emit(' hers.') : emit(' his.'); %}
This example is a little contrived as you could also do
The car is{{ ctx.sex == 'female' ? ' hers.' : ' his.' }}
But it does come in useful sometimes, I swear.
###Provided functions See the API docs for what functions are available to you from your templates and how to extend that set.
Converting Templates to JavaScript
Documentation in development...
Rendering Templates
Documentation in development...