MeteorJS (http://meteor.com) is a Javascript server/client framework based on an Express server.
This article is part of a blog series:
- Part 1 - Introduction
- Part 2 - Getting Started
- Part 3 - Collections
- Part 4 - Router
- Part 5 - Templates
- Part 6 - Deployment & BlueMix
- Part 7 - Automated Testing
This article completely focuses on the client-side. The server side is completely ignored.
Meteor templates use the Handlebars template engine (http://handlebarsjs.com/), which is a derivative of Mustache templates (https://mustache.github.io/), primarily utilising curly brackets - { and } - to use template helpers (dynamically including data on runtime).
The HTML page rendered when you access a Meteor application is generated. The <html>, <head> and <body> tags are automatically created, where the <head> tag will contain includes for all scripts and a minified CSS file. In production mode the JavaScript files are minified too into one file.
If you want to set custom <meta> tags or any other <head> sub-elements you can create an HTML file (anywhere) like this:
<head> <title>My Page Title</title> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" /> </head>
The file name and location is irrelevant, because on startup Meteor scans the file system for HTML files, scanning for <head> tags and other tags - especially <template> tags. On page request when the <head> element is now generated by Meteor it will append the content of this <head> tag (the title and meta tag) to the automatically generated script includes.
Regular templates can be declared using the <template> tag in any HTML file:
<template name="mytemplate"> ... </template>
The identifier "mytemplate" is the reference to this template. The identifier will be used to include this template, declare helpers and events. Any HTML file can contain multiple template declarations, but it is recommended to only declare one template per file and name the file after the template instance - e.g. mytemplate.html declares the template "mytemplate". Meteor will also ensure that all templates are valid XHTML. In case of any violations (missing closed tags / invalid constructs), Meteor will show a detailed and very helpful error message on the server console.
Layout
In web applications there are ususally only a few layouts used to render pages. Assuming a simple case where you need a splash screen (e.g. for login) and the main layout containing some header with navigation.
Iron Router (explained in the previous part of this blog series) supports layout templates. Any template can be a layout template and dynamically used in the Iron Router routes:
<template name="full-layout"> <div id="header"> <!-- include header template --> {{> header}} </div> <div id="main"> <!-- include dynamic template --> {{> yield}} </div> </template> Router.route('/home', function() { this.layout('full-layout'); this.render('home'); });
Inside the route, we specify the layout template to render the body content (full-layout) and the main template for the current page (home). The layout template uses the {{> yield}} include, which is a fixed keyword to include the main template provided by this.render(templateName) in the route. So the include {{> yield}} becomes {{> home}} for this particular route.
Helpers
Template helpers are used to include data into placeholders in the template. Placeholders are defined with curly brackets as well: {{helperName}}. Next to the HTML file I recommend having a JavaScript file as well, which contains all helpers and events (see below). Ideally the name of the JavaScript file is the same as the name of the template file (to avoid chaos).
Template helpers can be declared in 2 different ways:
Template.mytemplate.helpers({ 'helperName': function() { return 'something'; }, 'anotherHelper': function(parameter) { return 'something else: ' + parameter; } });
or
Template.mytemplate.helperName = function() { return 'something'; } Template.mytemplate.anotherHelper = function(parameter) { return 'something else: ' + parameter; }
It is recommended to declare all helpers together (using the first way), primarily because there are certain keywords that cannot be used for helpers, because they are reserved keywords (like 'rendered' and 'events' - see below for more).
These helpers can be invoked in the template like this:
{{helperName}} {{anotherHelper 'myParameter'}} or {{anotherHelper localVariable}}
Placeholder template helpers will usually return string values that are printed out into the DOM. Besides that there are functional placeholders as well for iteration (each) and logical checks (if, else).
If you want to iterate over a list of JSON objects with a field "fieldName", you can print out this field for each element in the list:
{{#each helperNameReturningArray}} <span>{{this.fieldName}}</span> {{/each}}
Other helpers may return boolean variables and can be used for if and if not (unless) placeholders:
{{#if conditionHelperReturningBoolean}} ... {{/if}} {{#unless anotherConditionHelper}} ... {{/unless}}
You can also pass parameters to these functional placeholders.
Events
Events are also declared for a template. However, they are also valid for any template that is included by the template they are declared for, but not for the parent template. Assuming you have a layout template and a main content template (e.g. 'home' from above example). Events declared for the 'home' template are not available in the layout section (e.g. header). However, events declared for the layout template would also apply for the content of the 'home' template.
The syntax to declare events is:
Template.mytemplate.events({ '<eventType> <jQuerySelector>': function(event) { // handle the click event } });
For example:
Template.mytemplate.events({ 'click #main #region-list button.add': function(event) { // handle button click event } });
Rendered
To capture the jQuery equivalent of $(document).ready(function() { ... }); Meteor provides the rendered event, which is declared slighly different than the events and helpers:
Template.mytemplate.rendered = function() { // perform on $(document).ready actions }
Additional template properties (event hooks) are:
- Template.mytemplate.created = function() { ... } - called before rendering the page
- Template.mytemplate.destroyed = function() { ... } - called when leaving the page.
Global events and helpers
To make some helper collection results available globally, you may want to declare global helpers. In a similar way you may also have the need for global events (e.g. any click on a button triggers some animation). Global helpers are declared for the Template object:
Template.registerHelper('helperName', function() { return 'something'; });
Global events are declared outside of the regular template context as simple jQuery events, which are registered on request:
Meteor.startup(function () { $('#main #region-list button.add').on('click', function(event) { // handle button click event } });
It is recommended to use local template events, to avoid triggering events accidentally. Global events should really only be used if they are required in multiple templates.
Views
A view usually consists of an HTML file (declaring the template), a JavaScript file (for events and helpers) and a CSS/LESS file for the styling. A view can have sub-views, which follow the same logic. This makes organizing the file system fairly easy. A recommended file structure for 2 views with one sub-view for the second view would be:
<project-root> + client + views + viewname1 + viewname1.html + viewname1.js + viewname1.less + viewname2 + view2-subview + view2-subview.html + view2-subview.js + view2-subview.less + viewname2.html + viewname2.js + viewname2.less
Further Reading:
- http://meteortips.com/book/templates/ (Tutorial for templates in great detail)