{{Template7}}
Mobile-first JavaScript Template Engine

Download

MIT Licensed, v1.1.0 released on October 3, 2015 | Changelog

Template7

Template7 is a mobile-first JavaScript template engine with Handlebars-like syntax. It is used as default template engine in Framework7

It is ultra lightweight (around 1KB minified and gzipped) and blazing fast (up to 2-3 times faster than Handlebars in mobile Safari).

Download and install Template7

First of all we need to download required Template7 files:

In the downloaded/installed package we need JavaScript files (.js) from the dist/ folder.

And include required script in our HTML page:

<html>
    <head>
        ...
        <script src="path/to/template7.min.js"></script>
    </head>
    <body>
        ...
    </body>
</html>          

Templates

Template7 templates looks like Handlebars templates, it is like regular HTML but with embedded handlebars expressions:

<div class="list-block">
  <ul>
    {{#each items}}
    <li class="item-content">
      <div class="item-inner">
        <div class="item-title">{{title}}</div>
      </div>
    </li>
    {{/each}}
  </ul>
</div> 

Expressions syntax

Template7 support expressions with the following syntax:

Variables:

Block expressions

Helpers

Helpers could be plain expressions and block expressions:

Compilation and Rendering

Template7 is a globally available Window function.

First of all we need to deliver string template. For example, we can store in script tag:

<script id="template" type="text/template7">
    <p>Hello, my name is {{firstName}} {{lastName}}</p>
</script>

Now we need to compile it in JavaScript. Template7 will convert our template string to plain JavaScript function:

var template = $$('#template').html();
 
// compile it with Template7
var compiledTemplate = Template7.compile(template);
 
// Now we may render our compiled template by passing required context
var context = {
    firstName: 'John',
    lastName: 'Doe'
};
var html = compiledTemplate(context);

Now, html variable will contain:

<p>Hello, my name is John Doe</p>

Built-In Helpers

Helpers in Template7 are like predefined functions that do something with passed context.

{{#each}}...{{else}}...{{/each}}

{{#each}} is a block expression, that iterates through items of passed Array or through properties of passed Object.

The following additional variables are available inside of this helper:

Template -> Context -> Output
Iterate through Array items
<p>Here are the list of people i know:</p>
<ul>
  {{#each people}}
  <li>{{firstName}} {{lastName}}</li>
  {{/each}}    
</ul>          
{
  people : [
    {
      firstName: 'John',
      lastName: 'Doe'
    },
    {
      firstName: 'Mark',
      lastName: 'Johnson'
    },
  ]
}      
<p>Here are the list of people i know:</p>
<ul>
  <li>John Doe</li>
  <li>Mark Johnson</li>
</ul>    
<p>Here are the list of people i know:</p>
<ul>
  {{#each people}}
  <li>[email protected]}}. {{this}}</li>
  {{/each}}    
</ul>          
{
  people : ['John Doe', 'Mark Johnson']
}      
<p>Here are the list of people i know:</p>
<ul>
  <li>0. John Doe</li>
  <li>1. Mark Johnson</li>
</ul>  
Iterate through Object properties
<p>Car properties:</p>
<ul>
  {{#each props}}
  <li>[email protected]}}: {{this}}</li>
  {{/each}}
</ul>
{
  props: {
    power: '150 hp',
    speed: '200 km/h',
  }
}
<p>Car properties:</p>
<ul>
  <li>power: 150 hp</li>
  <li>speed: 200 kn/h</li>
</ul>
{{else}} expression.
<p>Car properties:</p>
<ul>
  {{#each props}}
  <li>[email protected]}}: {{this}}</li>
  {{else}}
  <li>No properties</li>
  {{/each}}
</ul>
{
  props: {
    power: '150 hp',
    speed: '200 km/h',
  }
}
<p>Car properties:</p>
<ul>
  <li>power: 150 hp</li>
  <li>speed: 200 kn/h</li>
</ul>
<p>Car properties:</p>
<ul>
  {{#each props}}
  <li>[email protected]}}: {{this}}</li>
  {{else}}
  <li>No properties</li>
  {{/each}}
</ul>
{}
<p>Car properties:</p>
<ul>
  <li>No properties</li>
</ul>

{{#if}}...{{else}}...{{/if}}

{{#if}} helper renders content if passed context is not "false" (or "undefined" or "null" or "" or "0") , otherwise it renders inverse content that optionally could be passed to {{else}} expression inside of helper:

Template -> Context -> Output
<a href="#" {{#if active}}class="active"{{/if}}>{{title}}</a>
{
  active: true,
  title: 'Link',
}
<a href="#" class="active">Link</a>
{{else}} expression.
<p>Hello, my name is {{name}}.</p>
{{#if hobby}}
<p>I have hobby</p>
{{else}}
<p>I don't have hobby</p>
{{/if}}                  
{
  name: 'John Doe',
  hobby: false
}
<p>Hello, my name is John Doe.</p>
<p>I don't have hobby</p>

{{#unless}}...{{else}}...{{/unless}}

{{#unless}} helper renders content if passed context is "false" (or "undefined" or "null" or "" or "0") , otherwise it renders inverse content that optionally could be passed to {{else}} expression inside of helper:

Template -> Context -> Output
<a href="#" {{#unless active}}class="active"{{/unless}}>{{title}}</a>
{
  active: true,
  title: 'Link',
}
<a href="#">Link</a>
{{else}} expression.
<p>Hello, my name is {{name}}.</p>
{{#unless hobby}}
<p>I have hobby</p>
{{else}}
<p>I don't have hobby</p>
{{/unless}}                  
{
  name: 'John Doe',
  hobby: false
}
<p>Hello, my name is John Doe.</p>
<p>I have hobby</p>

{{#with}}...{{/with}}

{{#with}} helper changes rendering context to the passed context:

Template -> Context -> Output
{{#with props}}
<p>Car has {{power}} power and {{speed}} maximum speed</p>
{{/with}}
{
  props: {
    power: '150 hp',
    speed: '200 km/h',
  }
}
<p>Car has 150 hp power and 200 km/h maximum speed</p>

{{#variableName}}...{{/variableName}}

If you pass a block expression with helper name that is in the expression context, then it will work like {{#each}} helper for this context if it is an Array, and will work like {{#with}} helper if it is an Object:

Template -> Context -> Output
<ul>
  {{#people}}
  <li>{{name}} - {{age}} years old</li>
  {{/people}}
</ul>
{
  people: [
    {
      name: 'John Doe',
      age: 18
    },
    {
      name: 'Mark Johnson',
      age: 21
    }
  ]  
}
<ul>
  <li>John Doe - 18 years old</li>
  <li>Mark Johnson - 21 years old</li>
</ul>
{{#props}}
<p>Car has {{power}} power and {{speed}} maximum speed</p>
{{/props}}
{
  props: {
    power: '150 hp',
    speed: '200 km/h',
  }
}
<p>Car has 150 hp power and 200 km/h maximum speed</p>

{{join delimiter=""}}

This plain helper will join Array items to single string with passed delimiter

Template -> Context -> Output
<h3>"{{title}}" TV Show</h3>
<p>Was released in year {{year}}</p>
<p>Genres: {{join genres delimiter=", "}}</p>
{
  title: 'Friends',
  year: 2001,
  genres: ['comedy', 'drama']
}
<h3>"Friends" TV Show</h3>
<p>Was released in year 2001</p>
<p>Genres: comedy, drama</p>

{{escape}}

This plain helper returns escaped HTML string. It escapes only the following characters: < > " &

Template -> Context -> Output
<h1>{{title}}</h1>
<p>{{escape body}}</p>
{
  title: 'Paragraphs',
  body: 'We need to use <p> tags to add paragraphs in HTML',
}
<h1>Paragraphs</h1>
<p>We need to use &lt;p&gt; tags to add paragraphs in HTML</p>

{{js "expression"}}

This inline helper allows to execute some simple JavaScript directly in template to modify/check context on the fly or for some JS calculations

Template -> Context -> Output
<h3>{{title}}</h3>
<p>Price: ${{js "this.price * 1.2"}} </p>
<p>{{js "this.inStock ? 'In Stock' : 'Not in stock'"}} </p>
{
  title: 'iPhone 6 Plus',
  price: 1000,
  inStock: true
}
<h3>iPhone 6 Plus</h3>
<p>Price: $1200</p>
<p>In stock</p>

{{#js_compare "expression"}}...{{/js_compare}}

Block helper for easier compares of context variables. It renders content if JavaScript expression is not "false" (or "undefined" or "null" or "" or "0") , otherwise it renders inverse content that optionally could be passed to {{else}} expression inside of helper

Template -> Context -> Output
<h3>{{title}}</h3>
<p>Price: ${{price}} </p>
<p>{{#js_compare "color === 'white' && memory > 16"}}Not in stock{{else}}In stock{{/js_compare}} </p>
{
  title: 'iPhone 6 Plus',
  price: 1000,
  color: 'white',
  memory: 32
}
<h3>iPhone 6 Plus</h3>
<p>Price: $1000</p>
<p>Not in stock</p>
<p>{{#js_compare "a === b"}}A equals to B{{else}}A not equal to B{{/js_compare}} </p>
{
  a: 5,
  b: 34
}
<p>A not equal to B</p>

Nore that in js and in js_compare helper you may need to use this.variableName instead of just variableName

Using Custom Helpers

Template7 allows to register custom helpers with the following method:

Template7.registerHelper(name, helper)

Helper function could accepts as many arguments as required, arguments could be context, strings and hash data.

Let's look how to register helper on example of simple {{#if}} helper:

Template7.registerHelper('if', function (condition, options) {
  // "this" in function context is equal to the expression execution context
  // "condition" argument contains passed context/condition
  /*
    @options contains object with the wollowing properties and methods:
    "hash" - contains passed hash object with parameters
    "fn" - method to pass helper block content further to compilier
    "inverse" - method to pass helper block inverse ({{else}}) content further to compilier
    "data" - contains additional expression data, like @index for arrays or @key for object
  */
 
  // First we need to check is the passed context is function
  if (typeof condition === 'function') condition = condition.call(this);
 
  // If context condition
  if (condition) {
    // We need to pass block content further to compilier with the same context and the same data:
    options.fn(this, options.data);
  }
  else {
    // We need to pass block inverse ({{else}}) content further to compilier with the same context and the same data:
    options.inverse(this, options.data);
  }
}); 

Or on example of plain {{join}} helper:

Template7.registerHelper('join', function (arr, options) {
  // First we need to check is the passed arr argument is function
  if (typeof arr === 'function') arr = arr.call(this);
 
  /* 
    Passed delimiter is in the options.hash object:
    console.log(options.hash) -> {delimiter: ', '}
  */
 
  // And return joined array
  return arr.join(options.hash.delimiter);
});          

Or we can create helper to create Framework7's list-block link to work with this syntax:

{{link url title target="_blank"}}
Template7.registerHelper('link', function (url, title, options){
  var ret = '<li>' +
              '<a href="' + url + '" class="item-content item-link" target="' + options.hash.target + '">' +
                '<div class="item-inner">' +
                  '<div class="item-title">' + title + '</div>' +
                '</div>' +
              '</a>' +
            '</li>';
  return ret;
});
Template -> Context -> Output
<div class="list-block">
  <ul>
    {{#each links}}
    {{link url title target="_blank"}}
    {{/each}}    
  </ul>
</div>
{
  links: [
    {
      url: 'http://google.com',
      title: 'Google'
    },
    {
      url: 'http://idangero.us',
      title: 'iDangero.us'
    },
  ]
}
<div class="list-block">
  <ul>
    <li>
      <a href="http://google.com" target="_blank" class="item-link item-content">
        <div class="item-inner">
          <div class="item-title">Google</div>
        </div>
      </a>
    </li>
    <li>
      <a href="http://idangero.us" target="_blank" class="item-link item-content">
        <div class="item-inner">
          <div class="item-title">iDangero.us</div>
        </div>
      </a>
    </li>
  </ul>
</div>

Note, that all custom helpers should be registered before you compile templates with these helpers!

Remove Custom Helpers

Template7 allows to remove custom helpers with the following method:

Template7.unregisterHelper(name)

Global Context

Template7 also supports global context which is accessible from any context.

We can specify it in Template7.global property:

Template7.global = {
    os: 'iOS',
    browser: 'Chrome',
    username: 'johndoe',
    email: [email protected]'
};

To access it in templates we need to use [email protected]}} variable:

<p>Hello, [email protected]}}. Your email is [email protected]}}</p>

Access To Root Context

Sometimes we may need to access to initially passed root context in our templates. For this case we need to use [email protected]}} variable. This is especially helpful when we are deep in context:

{
    persons: [
        {
            name: 'John',
            hobby: ['Cars', 'Food']
        },
        {
            name: 'Kyle',
            hobby: ['Travel', 'Puzzles']
        },
 
    ],
    showHobby: true
}    
{{#each persons}}
    <h2>{{name}}</h2>
    <h3>Hobby:</h3>
    {{#if @root.showHobby}}
        <ul>
            {{#each hobby}}
                <li>{{this}}</li>
            {{/each}}
        </ul>
    {{/if}}
{{/each}} 

Partials

Template7 allows to reuse template using through partials. Partials are normal usual Template7 templates that may be called by other templates.

We can register and unregister partials using the following methods:

Template7.registerPartial(name, template) - register partial

Template7.unregisterPartial(name) - unregister partial

Then we can use our partials using special helper {{> "partialName"}}

Template:

<ul class="users">
    {{#each users}}
    {{> "user"}}
    {{/each}}
</ul>
<ul class="admins">
    {{#each admins}}
    {{> "user"}}
    {{/each}}
</ul>

Register partial:

Template7.registerPartial('user', '<li><h2>{{firstName}} {{lastName}}</h2><p>{{bio}}</p></li>')

Apply to the template this context:

{
    users: [
        {
            firstName: 'John',
            lastName: 'Doe',
            bio: 'Lorem ipsum dolor'
        },
        {
            firstName: 'Jane',
            lastName: 'Doe',
            bio: 'Donec sodales euismod augue'
        }
    ],
    admins: [
        {
            firstName: 'Mike',
            lastName: 'Doe',
            bio: 'Lorem ipsum dolor'
        },
        {
            firstName: 'Kate',
            lastName: 'Doe',
            bio: 'Donec sodales euismod augue'
        }
    ]
}

And we will get the following output:

        
<ul class="users">
    <li>
        <h2>John Doe</h2>
        <p>Lorem ipsum dolor</p>
    </li>
    <li>
        <h2>Jane Doe</h2>
        <p>Donec sodales euismod augue</p>
    </li>
</ul>
<ul class="admins">
    <li>
        <h2>Mike Doe</h2>
        <p>Lorem ipsum dolor</p>
    </li>
    <li>
        <h2>Kate Doe</h2>
        <p>Donec sodales euismod augue</p>
    </li>
</ul>    

Recursive Partials

We can even use partials to make recursive templates, like nested comments:

// Simple template with just a partial
var template = '{{> "comments"}}'
 
// Register partial
Template7.registerPartial(
    'comments', 
    '<ul>' + 
        '{{#each comments}}' +
            '<li>' +
            '<h2>{{author}}</h2>' +
            '<p>{{text}}</p>' +
            '{{#if comments}}{{> "comments"}}{{/if}}' +
            '</li>' +
        '{{/each}}' +
    '</ul>'
);
 
// Compile template 
var compiledTemplate = Template7.compile(template);
 
// Render template 
var output = compiledTemplate({
    comments: [
        {
            author: 'John Doe',
            text: 'Lorem ipsum dolor',
            comments: [
                {
                    author: 'Mike Doe',
                    text: 'Aliquam erat volutpat'
                },
                {
                    author: 'Kate Doe',
                    text: 'Donec eget fringilla turpis'
                }
            ]
        },
        {
            author: 'Jane Doe',
            text: 'Donec sodales euismod augue'
        }
    ]
})

And the output will be:

<ul class="comments">
    <li>
        <h2>John Doe</h2>
        <p>Lorem ipsum dolor</p>
        <ul class="comments">
            <li>
                <h2>Mike Doe</h2>
                <p>Aliquam erat volutpat</p>
            </li>
            <li>
                <h2>Kate Doe</h2>
                <p>Donec eget fringilla turpis</p>
            </li>
        </ul>
    </li>
    <li>
        <h2>Jane Doe</h2>
        <p>Donec sodales euismod augue</p>
    </li>
</ul>          

Performance Tips

Template7 is fast and you can make it even faster in your apps. The slowest part (but still very fast in T7) in compilation/rendering process is the compilation from string to pure JS function when you do Template7.compile(). So don't compile the same templates multiple times, one time will be enough:

// Begin of your app
 
// Compile templates once on app load/init
var searchTemplate = $('script#search-template').html();
var compiledSearchTemplate = Template7.compile(searchTemplate);
 
var listTemplate = $('script#list-template').html();
var compiledListTemplate = Template7.compile(listTemplate);
 
// That is all, now and further just execute compiled templates with required context
// Just execute compiled search template with required content:
var html = compiledSearchTemplate({/*...some data...*/});
 
// Do something with html...