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.
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 [[#Using Template.Micro|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):
```javascript YUI().use('template', function (Y) { var micro = new Y.Template(), html = micro.render('<%= this.message %>', {message: 'hello!'}); Y.log(html); // => "hello!" }); ```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:
```javascript YUI().use('template-base', 'handlebars', function (Y) { var handlebars = new Y.Template(Y.Handlebars), html = handlebars.render('\{{message}}', {message: 'hello!'}); Y.log(html); // => "hello!" }); ```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.
`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.
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 [[#Using Template.Micro|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. |
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:
```javascript 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(); }); ```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:
```javascript 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:
```javascript 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); }); ```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 [[#Precompiling and Reviving Templates|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.
```javascript 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.
```javascript // 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.
```javascript // 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.
```javascript // 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 %>`.
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 [[#Precompiling and Reviving Micro Templates|Template.Micro]] and Handlebars user guides.
The generic `Y.Template` interface is [[#Generic Template API|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:
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.
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.
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.
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.
The template registry decouples making templates available from invoking a template to render it. This central registry and abstraction of templates to names separates concerns, creates a level of indirection, and enables templates to be easily overridden.
When precompiling templates, the template registry can be used as an abstraction layer allowing application code to render templates without having to know about the underlying template engine or what component is providing the template and its location on that component. This creates a clear separation between allowing the code that consumes a template to reference it by an abstracted template name in the registry which is registered by a dependency, like a YUI module.
This central template registry is used through two static methods on `Y.Template`:
Registers a pre-compiled template into the central template registry with a given template string, allowing that template to be called and rendered by that name using the `Y.Template.render()` static method.
Returns a revived template function registered under the given `templateName`, if it exists. Otherwise, it returns `undefined`.
Renders a registered template under the given `templateName`, passing along any additional `data` or `options` values needed.
Using template registration usually involves taking a precompiled template, and wrapping it as a YUI module. You can use packages such as locator-handlebars to do so automatically, by having it find the location of your existing templates. The wrapped pre-compiled template should look something like this:
foo.handlebars ```html\{{tagline}}
``` templates-foo.js ```javascript YUI.add('templates-foo', function (Y) { var engine = new Y.Template(Y.Handlebars), precompiled; // Generated from the Handlebars compiler. This is the evaluated result of // precompiling `foo.handlebars`. precompiled = function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,\'>= 1.0.0\'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression;\n\n\n buffer += "";\n if (stack1 = helpers.tagline) { stack1 = stack1.call(depth0, {hash:{},data:data}); }\n else { stack1 = depth0.tagline; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }\n buffer += escapeExpression(stack1)\n + "
";\n return buffer;\n }; Y.Template.register('foo', engine.revive(precompiled)); }, '0.0.1', {requires: ['template-base', 'handlebars-base']}); ```Then, once the precompiled template is revived and registered, you can simply render the template in the following way:
bar.js ```javascript YUI.add('bar', function (Y) { var html = Y.Template.render('foo', { tagline: '"bar" is now template language agnostic' }); }, '0.0.1', {requires: ['template-base', 'templates-foo']}); ``` Our template can now be used as a dependency like any other YUI module, and rendered without needing the full Handlebars compiler (which is much larger in file size than `handlebars-base`) or even having to know which template engine is being used in the application code.`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 Template API|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.
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:
```This tells Template.Micro:
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.
Otherwise, insert an empty string.
The following example shows how the data context is defined when executing a Micro template function:
```javascript var micro = new Y.Template(), heading = micro.compile('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:
```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.
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 | |
---|---|
```html
Animals
|
|
Data | Output |
```javascript { classNames: {list: 'animals'}, animals: [ 'Rhino', 'Plain Tiger butterfly', 'Spotted Cuscus' ] } ``` |
```html
Animals
|
Precompiling Micro templates [[#Precompiling and Reviving 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):
```terminal $ 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.
```javascript // 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:
```javascript 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:
```javascript #!/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( '' + '' + '' + '' ); }); 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/.
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.
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.
Template Source | |
---|---|
```html
Animals
|
|
Data | Output |
```javascript { classNames: {list: 'animals'}, animals: [ 'Rhino', 'Plain Tiger butterfly', 'Spotted Cuscus' ] } ``` |
```html
Animals
|
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|generic Template API]], doing so will only affect how the templates are process for that engine instance.
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.
This example shows how to create a very basic view with an embedded template:
```javascript 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( '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.
One option is to embed your template inside a special ` ``` ```javascript 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 `
`. var animalListView = new Y.AnimalListView({ animals: [ 'Rhino', 'Plain Tiger butterfly', 'Spotted Cuscus' ] }); animalListView.get('container').appendTo('body'); }); ```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`:
```javascript // 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( 'Refer to the Creating YUI Modules user guide for more details.
The following is a list of best practices to consider when using templates in your app and/or custom components:
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.
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 [[#Defining Templates in a Module|template module example]] above would ideally use a build-time process to wrap the template source with the YUI module registration wrapper.
Template [[#Instantiating a Template Engine|compilation is expensive]]. You should avoid compiling a template more than once. Ideally, you are [[#Precompiling and Reviving Templates|precompiling templates]] on the server or during a build-time process to avoid the compilation step on the client.