Jump to Table of Contents

Template

The Template component provides Y.Template, a generic template engine API, and Y.Template.Micro, a string-based micro-templating language similar to ERB and Underscore templates.

Getting Started

To include the source files for Template and its dependencies, first load the YUI seed file if you haven't already loaded it.

<script src="http://yui.yahooapis.com/3.11.0/build/yui/yui-min.js"></script>

Next, create a new YUI instance for your application and populate it with the modules you need by specifying them as arguments to the YUI().use() method. YUI will automatically load any dependencies required by the modules you specify.

<script>
// Create a new YUI instance and populate it with the required modules.
YUI().use('template', function (Y) {
    // Template is available and ready for use. Add implementation
    // code here.
});
</script>

For more information on creating YUI instances and on the use() method, see the documentation for the YUI Global Object.

Using Template

Quick Start

A template engine takes a template—usually in the form of a string—and some data, and renders the data into the template to produce an HTML or text string. Using templates to keep markup and structure separate from content encourages reuse and can make code easier to read and maintain, and in many cases faster.

Y.Template provides a common API that can be used to compile and render templates with a variety of template engines. The two template engines included in YUI are Handlebars and Template.Micro.

The quickest way to get started is using the template module which will load both the template-base and template-micro modules. The following example shows the most basic usage with the Y.Template.Micro engine (the default template engine):

YUI().use('template', function (Y) {
    var micro = new Y.Template(),
        html  = micro.render('<i><%= this.message %></i>', {message: 'hello!'});

    Y.log(html); // => "<i>hello!</i>"
});

In the above example, micro is an instance of a template engine backed by Template.Micro. The Y.Template() constructor provides an abstraction over the backing engine, giving the engine instances a uniform API.

Handlebars templates can be used instead of Micro templates by using the template-base and handlebars modules. The following example shows how to generate the same output as the above example with the Handlebars engine:

YUI().use('template-base', 'handlebars', function (Y) {
    var handlebars = new Y.Template(Y.Handlebars),
        html       = handlebars.render('<i>{{message}}</i>', {message: 'hello!'});

    Y.log(html); // => "<i>hello!</i>"
});

Note: Both examples are using the engine's render() method to compile and render the template dynamically on the client, doing this with Micro templates is fine, but it should be avoided with Handlebars templates. It is recommended that Handlebars templates be precompiled, enabling the client code to use the lighter and faster handlebars-base module.

Generic Template API

Y.Template exists specifically to provide its API as a normalization layer on top of conceptually similar, but technically different template engines and syntaxes. This layer of abstraction allows components which work with templates to not be tied to a particular engine. Another huge benefit is allowing developers to override a component's default templates using an entirely different template engine.

The two template engines provided in YUI, Handlebars and Template.Micro, are conceptually similar. They both compile string-based templates into functions, which are invoked with a data context and return the rendered output as a string. Handlebars is really well suited for organizing and managing the templates of an entire app or complex widget because of its partials and helpers features. Template.Micro is great for small templates, or when you need more powerful templates and its compilation engine is extremely small.

By making Template.Micro's public API very similar to Handlebars, we've made it possible to use the two template engines interchangeably via the Y.Template normalization API. When you need to compile templates on the client, it is strongly recommend that you use Micro templates, because Template.Micro's compiler is much smaller than Handlebars' compiler — 0.5KB vs 9KB (minified and gzipped) respectively.

Instantiating a Template Engine

While you can use a specific template engine directly, it is recommended that you create an instance of the generic Y.Template engine wrapper. Doing so allows for greater flexibility and interoperability as described in the previous section.

To create a template engine instance, you must first determine which underlying engine you want to use. The two template engines included in YUI are Handlebars and Template.Micro. If you're looking to use a different engine, refer to Creating a Custom Template Engine section below.

Once you've determined the underlying template engine, you'll need to load the appropriate YUI module to fulfill how you plan to use templates. Refer to the following table of YUI modules to understand what each module provides:

Module Compiler Description
template-base No

Provides a generic API for using template engines such as Handlebars and Y.Template.Micro.

template-micro Yes

Adds the Y.Template.Micro template engine, which provides fast, simple string-based micro-templating similar to ERB or Underscore templates.

template Yes

Virtual rollup of the template-base and template-micro modules.

handlebars-base No

Provides basic Handlebars template rendering functionality. Use this module when you only need to render pre-compiled templates.

handlebars-compiler Yes

Handlebars parser and compiler. Use this module when you need to compile Handlebars templates.

handlebars Yes

Virtual rollup of the handlebars-base and handlebars-compiler modules.

Using Micro Templates

When working with Micro templates, it's easiest to use the template virtual rollup module. The Y.Template.Micro compiler is small enough that it is included with the runtime functionality.

The following example creates two template engine instances with are functionally equivalent and both backed by Template.Micro:

YUI().use('template', function (Y) {
    var microExplicit, microDefault;

    // Creates a template engine instance and explicitly specifies the
    // underlying engine.
    microExplicit = new Y.Template(Y.Template.Micro);

    // Creates another template engine instance with the same functionality,
    // but relies on `Y.Template.Micro` being defined as the underlying engine
    // by default.
    microDefault = new Y.Template();
});

Using Handlebars Templates

When working with Handlebars templates, you'll need to determine if the need the Handlebars compiler functionality provided by the handlebars-compiler module. It is recommended that Handlebars templates be precompiled, enabling the client code to use the lighter and faster handlebars-base module.

The following example loads only the Handlebars runtime and generic Y.Template() wrapper API. It assumes that all templates have previously been precompiled on the server or during a build step:

YUI().use('template-base', 'handlebars-base', function (Y) {
    // Creates a limited template engine instance using Handlebars as the
    // underlaying engine, but with only the runtime functionality.
    var handlebars = new Y.Template(Y.Handlebars);
});

Note: In the above example, the handlebars engine does not have the ability to render(), compile(), or precompile() template. It only has the ability to revive() and execute precompiled templates.

The following example, uses the handlebars virtual rollup module which includes the handlebars-compiler. This enables the Handlebars-backed template engine instances to use the full API:

YUI().use('template-base', 'handlebars', function (Y) {
    // Creates a template engine instance using Handlebars as the underlaying
    // engine, with both the runtime and compiler functionality.
    var handlebars = new Y.Template(Y.Handlebars);
});

Compiling and Rendering Templates

Both Handlebars and Micro templates must be compiled before they can be rendered. One benefit of this is that a template only needs to be compiled once, and it can then be rendered multiple times without being recompiled. Templates can even be precompiled on the server or at build time and then rendered on the client for optimal performance.

Before compiling a template string, a template engine needs to be created. Once the engine instance has been created, the template string can be passed to its compile() method. What's returned is a reusable function.

var engine, template;

// Create a Template.Micro engine instance.
engine = new Y.Template();

// Compile a template into a reusable function.
template = engine.compile('My favorite animal is a <%= this.animal %>.');

When you're ready to render the template, execute the function and pass in some data. You'll get back a rendered string.

// Render a previously compiled template.
var output = template({animal: 'Rhino'});
Y.log(output); // => "My favorite animal is a Rhino."

You can re-render the template at any time just by calling the function again. You can even pass in completely different data.

// Re-render a previously compiled template.
output = template({animal: 'Spotted Cuscus'});
Y.log(output); // => "My favorite animal is a Spotted Cuscus."

If you don't plan to use a template more than once, you can compile and render it in a single step with the template engine's render() method.

// Compile and render a template in a single step.
output = engine.render('My favorite animal is a <%= this.animal %>.', {animal: 'Rhino'});
Y.log(output); // => "My favorite animal is a Rhino."

Note: The above examples are using Micro templates. If these examples used Handlebars templates, the engine instance would have been created using Y.Handlebars, and template syntax would have used {{animal}} instead of <%= this.animal %>.

Precompiling and Reviving Templates

Since Micro and Handlebars templates can be compiled and rendered in separate steps, it's possible to precompile a template for use later. You can precompile a template into raw JavaScript on the server (or even on the command line in the case of Handlebars), serve this precompiled JavaScript template to the client, and then render it on the client using any data the client has at its disposal.

The main benefit of precompilation is performance. Not only does the client not need to go through the compile step, but if your using a template engine like Handlebars, you don't even have to load the compiler on the client! All the client needs in order to render a precompiled template is the engine's runtime. In the case of Handlebars this is a 9KB (minified and gzipped) savings.

Y.Template engine instances have a precompile() method which uses the underlaying engine to convert the specified text into a string of JavaScript source code. This string of code which represents the template, can later be revived using the engine instance's revive() method which turns it into a JavaScript function.

The precompile() method differs from the compile() method in a couple of important ways:

  • The precompile() method returns a string of JavaScript code that's meant to be parsed and executed later, whereas compile() returns a live JavaScript function.

  • The code returned by the precompile() method contains no references to any outside objects. Once it's evaluated, the resulting precompiled function must be passed to Y.Template engine instance's revive() method, which will "rehydrate" it into an executable template function using the current template engine.

For more details, refer to the Precompiling and Reviving Templates sections of the Template.Micro and Handlebars user guides.

Creating a Custom Template Engine

The generic Y.Template interface is designed to work with a variety of string -> function template engines. To implement a custom underlaying template engine for Y.Template, refer to the following list of methods and their descriptions which need to be implemented:

compile( text , [options] )

Compiles a string template into a reusable function and returns that function to the caller.

The core concept of a string -> function template engine is its compilation method. A custom template engine must implement this method.

render( text , data , [options] )

Compiles and renders a template in a single step, and returns the rendered result.

A custom template engine does not have to implement this method. It is merely provided as a convenience to the user. If the underlying engine does not implement this method, the compile() method will be called and the resulting function will be invoked.

precompile( text , [options] )

Precompiles a string template into a new string containing JavaScript source code for the precompiled template and returns it to the caller. The revive() method is this method's companion, it converts the precompiled template back into a renderable function.

A custom template engine does not have to implement this method. If precompilation is a feature of the underlying template engine, then the revive() method must also be implemented.

revive( precompiled , [options] )

Revives a precompiled template function into an executable template function and returns that function to the caller. The precompiled code must already have been evaluated; this method won't evaluate it for you.

This is a companion method to the precompile() method and it must be implemented if the underlying template engine supports precompilation.

Using Template.Micro

Y.Template.Micro is a string-based micro-templating language similar to ERB and Underscore templates. Template.Micro is great for small, powerful templates, and its compilation engine is extremely fast with a small footprint.

Compared with the features of Handlebars, Template.Micro is much simpler. Using the generic engine API provided by Y.Template, Micro and Handlebars templates can be used interchangeably. This gives you a powerful way to customize a component's Handlebars templates by overriding them with Micro templates, and not incur the cost of loading the handlebars-compiler module.

Template Syntax

Basic Expressions

Within a Micro template, use <%= ... %> to output the value of an expression (where ... is the JavaScript expression or data variable to evaluate). The output will be HTML-escaped by default.

A simple Template.Micro expression looks like this:

<h1><%= this.title %></h1>

This tells Template.Micro:

  1. if there exists a title property in the current context in which the template function was executed, and that property is not falsy or an empty array, insert its value here.

  2. Otherwise, insert an empty string.

The following example shows how the data context is defined when executing a Micro template function:

var micro   = new Y.Template(),
    heading = micro.compile('<h1><%= this.title %></h1>'),
    output;

// The object passed to the template function becomes the context in which the
// template is executed. This object is also available through the `data`
// variable within the template's expressions.
output = heading({title: 'The Adventures of the Spotted Cuscus'});
Y.log(output); // => "<h1>The Adventures of the Spotted Cuscus</h1>"

Note: The template functions are call()-ed with the context of the object which is passed to the template function. This object is also available through the data variable. Therefore, data === this, within the template expressions. The previous template could have been written as:

<h1><%= data.title %></h1>

HTML Escaping

By default, content rendered using a percent-equals expression like <%= foo %> will automatically be HTML-escaped for safety. To render unescaped HTML output, use a percent-double-equals expression like <%== foo %>. Only use a percent-double-equals expression for content you trust! Never use it to render unfiltered user input.

Inline Code & Code Blocks

To execute arbitrary JavaScript code within the template without rendering its output, use <% ... %> (where ... is the code to be executed). This allows the use of if/else blocks, loops, function calls, etc., although it's recommended that you avoid embedding anything beyond basic flow control logic in your templates.

Template Source
<h1>Animals</h1>

<ul class="<%= this.classNames.list %>">
  <% Y.Array.each(this.animals, function (animal, i) { %>
    <li class="<%= i % 2 ? 'odd' : 'even' %>">
        <%= animal %>
    </li>
  <% }); %>
</ul>
Data Output
{
    classNames: {list: 'animals'},

    animals: [
        'Rhino',
        'Plain Tiger butterfly',
        'Spotted Cuscus'
    ]
}
<h1>Animals</h1>

<ul class="animals">
    <li class="even">Rhino</li>
    <li class="odd">Plain Tiger butterfly</li>
    <li class="even">Spotted Cuscus</li>
</ul>

Precompiling and Reviving Micro Templates

Precompiling Micro templates has advantages, especially when an app uses many templates. The rest of this section will demonstrate how to precompile templates on the server.

To precompile Micro templates on the server using Node.js, first install the YUI npm module by running the following in a terminal from the directory that contains your server application (this assumes you already have Node and npm installed):

$ npm install yui

This will install the yui npm module in the current directory and make it available to your application.

Next, in your application code, call the precompile() method to precompile a Micro template. It will return a string containing JavaScript code.

// Load the YUI Template.Micro module.
var Micro = require('yui/template-micro').Template.Micro;

// Precompile a template string (pass any string you like here).
var precompiled = Micro.precompile('My favorite animal is a <%= this.animal %>.');

The precompiled variable will contain a string of JavaScript code that looks something like this:

function (Y, $e, data) {
var $b='', $v=function (v){return v || v === 0 ? v : $b;}, $t='My favorite animal is a '+
$e($v( this.animal ))+
'.';
return $t;
}

You can now serve this precompiled JS to the client in whatever way makes the most sense for your application. On the client, load the template YUI module, create a Y.Template engine instance, and pass the precompiled template to its revive() method to convert it into a renderable template function.

Here's a simple Express app that precompiles a template on the server and renders it on the client:

#!/usr/bin/env node
var Micro   = require('yui/template-micro').Template.Micro,
    express = require('express'),
    app     = express(),

    precompiled = Micro.precompile('My favorite animal is a <%= this.animal %>.');

app.get('/', function (req, res) {
    res.send(
        '<html><body>' +
            '<script src="http://yui.yahooapis.com/3.11.0/build/yui/yui-min.js"></script>' +
            '<script>' +
                'YUI().use("template", function (Y) {' +
                    'var micro    = new Y.Template(),' +
                    '    template = micro.revive(' + precompiled + ');' +
                    'Y.one("body").append(template({animal: "Plain Tiger butterfly"}));' +
                '});' +
            '</script>' +
        '</body></html>'
    );
});

app.listen(7000);

To see this simple server in action, save it to a file, install Express and YUI by running npm i express yui, then execute the file with Node.js and browse to http://localhost:7000/.

Customizing Template Syntax

Micro templates have a simple syntax, there are only three forms:

<%= ... %>

Safely outputs the value of a JavaScript expression. The output will be HTML-escaped by default.

<%== ... %>

Outputs the raw value of a JavaScript expression. This does not HTML-escape the output. Never use it to render unfiltered user input.

<% ... %>

Executes arbitrary JavaScript code within the template without rendering its output.

These syntax identifiers are defined as RegExp on Y.Template.Micro.options. If you wish to define a custom syntax for your Micro templates, you can do so by defining new RegExps for your custom identifiers.

Defining a Handlebars-like Syntax

The following example will define a Handlebars-like Micro template syntax:

{{ ... }}

Safely outputs the value of a JavaScript expression. The output will be HTML-escaped by default.

{{{ ... }}}

Outputs the raw value of a JavaScript expression. This does not HTML-escape the output. Never use it to render unfiltered user input.

{{% ... %}}

Executes arbitrary JavaScript code within the template without rendering its output.

// Create RegExps which define our new Micro template syntax.
Y.mix(Y.Template.Micro.options, {
    code         : /\{\{%([\s\S]+?)%\}\}/g,
    escapedOutput: /\{\{(?!%)([\s\S]+?)\}\}/g,
    rawOutput    : /\{\{\{([\s\S]+?)\}\}\}/g
}, true);
Template Source
<h1>Animals</h1>

<ul class="{{ this.classNames.list }}">
  {{% Y.Array.each(this.animals, function (animal, i) { %}}
    <li class="{{ i % 2 ? 'odd' : 'even' }}">
        {{ animal }}
    </li>
  {{% }); %}}
</ul>
Data Output
{
    classNames: {list: 'animals'},

    animals: [
        'Rhino',
        'Plain Tiger butterfly',
        'Spotted Cuscus'
    ]
}
<h1>Animals</h1>

<ul class="animals">
    <li class="even">Rhino</li>
    <li class="odd">Plain Tiger butterfly</li>
    <li class="even">Spotted Cuscus</li>
</ul>

Note: The syntax identifiers can be specified on a per-template basis by passing options as the second argument to the compile(), precompile(), or render() methods. Alternatively you can specify defaults when using the generic Template API, doing so will only affect how the templates are process for that engine instance.

Using Templates in Custom Components

When creating custom components for your app, it's natural to bundle the templates with the component that will use them. The following examples show how to create a custom view component which uses templates.

Custom View with Embeded Template

This example shows how to create a very basic view with an embedded template:

YUI().use('template-micro', 'view', function (Y) {
    Y.AnimalListView = Y.Base.create('animalListView', Y.View, [], {
        // The compiled Micro template sits on the view's prototype.
        template: Y.Template.Micro.compile(
            '<ul class="animals">' +
              '<% Y.Array.each(this.animals, function (animal, i) { %>' +
                '<li class="<% i % 2 ? "odd" : "even" %>">' +
                    '<%= animal %>' +
                '</li>' +
              '<% }); %>' +
            '</ul>'
        ),

        render: function () {
            var html = this.template({
                animals: this.get('animals')
            });

            this.get('container').setHTML(html);
            return this;
        }
    });

    // Create an instance of the view and render it to the `<body>`.

    var animalListView = new Y.AnimalListView({
        animals: [
            'Rhino',
            'Plain Tiger butterfly',
            'Spotted Cuscus'
        ]
    });

    animalListView.get('container').appendTo('body');
});

Custom View with External Template

Usually embedding the template in your component's JavaScript code is bad practice. The following examples show two ways to externalize a component's templates.

Defining Templates in HTML

One option is to embed your template inside a special <script> element, one whose type attribute is set to something which the browser will not process as JavaScript:

<script id="t-animals" type="text/x-template">
    <ul class="animals">
      <% Y.Array.each(this.animals, function (animal, i) { %>
        <li class="<% i % 2 ? 'odd' : 'even' %>">
            <%= animal %>
        </li>
      <% }); %>
    </ul>
</script>
YUI().use('node-base', 'template-micro', 'view', function (Y) {
    Y.AnimalListView = Y.Base.create('animalListView', Y.View, [], {
        // The template source is pulled from the HTML, then compiled into a
        // template function which sits on the view's prototype.
        template: Y.Template.Micro.compile(Y.one('#t-animals').getHTML()),

        render: function () {
            var html = this.template({
                animals: this.get('animals')
            });

            this.get('container').setHTML(html);
            return this;
        }
    });

    // Create an instance of the view and render it to the `<body>`.

    var animalListView = new Y.AnimalListView({
        animals: [
            'Rhino',
            'Plain Tiger butterfly',
            'Spotted Cuscus'
        ]
    });

    animalListView.get('container').appendTo('body');
});

Defining Templates in a Module

When your custom component is used within multiple apps, you might not have control over the HTML of the page. A great option is to create a separate module which holds your template source which your view module can require:

// Defines a YUI module which will hold the template source for our view.
YUI.add('animalListTemplate', function (Y) {
    Y.namespace('AnimalListView').template = Y.Template.Micro.compile(
        '<ul class="animals">' +
          '<% Y.Array.each(this.animals, function (animal, i) { %>' +
            '<li class="<% i % 2 ? "odd" : "even" %>">' +
                '<%= animal %>' +
            '</li>' +
          '<% }); %>' +
        '</ul>'
    );
}, '0.0.1', {
    requires: ['template-micro']
});
// Defines a YUI module which defines our view and requires our template module.
YUI.add('animalListView', function (Y) {
    var AnimalListView = Y.Base.create('animalListView', Y.View, [], {
        render: function () {
            var html = AnimalListView.template({
                animals: this.get('animals')
            });

            this.get('container').setHTML(html);
            return this;
        }
    });

    // Properly exposes the view constructor while retaining the namespace.
    Y.AnimalListView = Y.mix(AnimalListView, Y.AnimalListView);
}, '0.0.1', {
    requires: ['animalListTemplate', 'view']
});
// Create an instance of the view and render it to the `<body>`.
YUI({
    modules: {
        animalListTemplate: '/animal-list-template.js',
        animalListView    : '/animal-list-view.js'
    }
}).use('animalListView', function (Y) {
    var animalListView = new Y.AnimalListView({
        animals: [
            'Rhino',
            'Plain Tiger butterfly',
            'Spotted Cuscus'
        ]
    });

    animalListView.get('container').appendTo('body');
});

Refer to the Creating YUI Modules user guide for more details.

Best Practices

The following is a list of best practices to consider when using templates in your app and/or custom components:

Less Logic is Better

Make sure not to embed too much logic in your templates, things can get out of control if you do. You should avoid template logic which has side effects! Micro templates allow you to embed any arbitrary JavaScript in your templates, while Handlebars templates are logic-less by design.

Externalize Templates

Avoid embedding huge template strings in your JavaScript code. Strive to separate your templates from the code that uses them, having your templates specified in separate files is best. The template module example above would ideally use a build-time process to wrap the template source with the YUI module registration wrapper.

Compile Once, Render Often

Template compilation is expensive. You should avoid compiling a template more than once. Ideally, you are precompiling templates on the server or during a build-time process to avoid the compilation step on the client.