-
Notifications
You must be signed in to change notification settings - Fork 7
Theme and template system
- Overview
- Setting the active theme
- The .iris.theme info file
- The theme folder
- Template lookups, naming and overrides
- Templating
- Parsing a template on the server side with
iris.modules.frontend.globals.parseTemplateFile
- Adding items to a templates Handlebars variables using
hook_frontend_template_parse
Iris has a powerful theme layer that creates markup from template files. Modules include templates which can be overridden - replaced - by the currently active theme to customise the display of content. Templates can be written using anything from plain HTML to Handlebars, AngularJS or another templating language of your choice.
The active theme is stored in a file called active_theme.json
in the site folder. This should contain the path to the theme and the theme name, as JSON, like so:
{"name": "mytheme"}
However, it is much easier to set the active theme in the admin interface at /admin/themes
, which checks all the valid locations for themes, displays a list and will assist with any dependencies a theme has.
For Iris to be able to find a theme, the theme folder needs to have a special JSON configuration file named after your theme. For example `mytheme.iris.theme'
In this file you will put all your theme's settings. Additional modules might allow further settings but here is an example configuration file. Dependencies lists iris modules that need to be enabled for this theme to work. Regions is a list of regions that are made available to the regions module.
{
"name": "My Iris theme",
"dependencies": {
"blocks": "1.0",
"regions": "1.0"
},
"regions": ["header", "footer"]
}
A theme is packaged up as a folder. The name of the folder is the system name of the theme.
To install themes, place them into an appropriate directory. The valid locations for a theme are:
-
/home/themes
- for custom or contributed themes you have installed yourself that should be available to all sites (if in doubt, do this) -
/home/sites/yoursite/themes
- for custom or contributed themes you have installed yourself that should only be available to one site -
/node_modules
- for contributed themes installed bynpm
-
/iris_core/themes
- for themes included with the Iris base package. Do not install your own themes here.
Before a theme can be used it must be enabled. Only one theme can be active at a time. Go to the themes section of the admin area to choose the active theme.
A base theme is bundled with Iris and should be useful to work from when creating a new one. If you would like to work from this theme, copy the folder from /iris_core/themes/base
into either /home/themes
or /home/yoursite/themes
and be sure to rename it to whatever you'd like. Theme names should only contain alphanumeric characters and underscores.
Note: Every theme you create or use for you site needs to have a unique name, or conflicts may occur. Remember to rename any copies of the base theme that you are working on!
Theme folders contain three parts.
-
static folder - Everything in this folder will be accessible directly through a URL. It's for CSS files, image files such as logos and front end JavaScript files for your theme. Anything in this directory will be accessible under:
yourappurl/theme/static/
- templates folder. This is where HTML templates (including Handlebars template code) are stored. See Template lookups, naming and overrides section for more inforamtion about how templates work.
Theming in Iris works using a system of template lookups and overrides. Markup is generated from templates that include Handlebars variables and embeds, a type of Iris-specific directive written using a triple-square-bracket notation, like [[[example embed]]]
. Default templates are provided by modules and then overridden by template files in the theme.
Template lookups are based on the file name of the template. Template lookups in Iris are done using a system based on folder location and double underscore __
divided sections. Here's how this works:
A theming function within the Iris system requests a template, passing through a list of parameters. For example, when requesting the template for an entity display (a page for example), these are:
- The entity type (page)
- The entity ID
A generic page.html template would match this lookup. If you wanted to use a different template for a specific page, you could simply add a double underscore and the entity ID to this filename. So page__5.html for example.
Menus and other templates work in a similar way. menu.html is the general menu template menu__menuname.html is more specific and takes priority.
The template system starts looking through the file system by looking at core and contributed Iris modules for a /templates folder. If a module has a templates folder it is checked for relevant templates. The later the module is loaded, the higher a priority it takes (see the module system documentation for information about module weighting).
The system then checks the templates folder of the currently active theme. These files take the top priority.
So modules and themes can easily override any template files set in the system or fall back to defaults if these exist.
The html
template, or html.html
, is used as the main wrapper template for the site. This template is a full HTML template that should include the opening <html>
tag, the header and the <body>
tag as well as any universally-used scripts or stylesheets in the page head. For many sites it is desirable that the same components are used everywhere, such as a menu or footer; this template can be used to display those also.
Each entity type has a template named after it; for example, the page
entity type has a template called page.html
. This will be used to render the entity's content and has the entity variables made available for it automatically.
Templates may be made more specific. Add a double underscore followed by the entity ID to an entity template's filename. page__4.html
would only be used for the entity with entity ID 4
, as another example.
Other templates include pages for common HTTP status codes: 404.html
, 403.html
, 500.html
. Replace these to customise the error pages that users see.
Iris uses a combination of Handlebars (http://handlebarsjs.com/) templating and its own square-bracketed embed language. Handlebars has its own extensive documentation which you would be advised to refer to. This document will only cover Iris specifics.
When viewing an entity page directly through its path, a Handlebars variable of current will be made available. The fields visible on this will depend on the field view permissions of the current user viewing the page.
An quick way of seeing which Handlebars variables are available for you to use the following Handlebars snippet:
{{#each this}}
{{@key}}
{{/each}}
Iris comes with a special hook (see the hook system documentation for information about hooks and modules) for extending Handlebars with helpers.
Here's an example from the messages part of the core Frontend module:
iris.modules.frontend.registerHook("hook_frontend_handlebars_extend", 0, function (thisHook, Handlebars) {
// Handle for a user's messages
Handlebars.registerHelper("iris_messages", function () {
var messages = iris.readMessages(thisHook.authPass.userid);
var output = "";
if (messages.length) {
output += "<ul class='iris-messages'>";
messages.forEach(function (message) {
output += "<li class='iris-message " + message.type + "'>" + message.message + "</li >";
});
output += "</ul>";
iris.clearMessages(thisHook.authPass.userid);
}
return output;
});
thisHook.pass(Handlebars);
})
Iris allows you to use any client side templating language (server side template languages can also be added by using hook_frontend_template
), however as some of these, such as AngularJS, clash with the delimiters of the handlebars templates (double curly brackets) you will possibly need to wrap your clashing template in a special escape wrapper to make it get ignored by Handlebars.
This is achieved using the iris_handlebars_ignore
wrapper in sets of four curly brackets.
Here's an example:
{{handlebarsVariable}} // this would be parsed by Handlebars on the server side.
{{{{iris_handlebars_ignore}}}}
{{angularVariable}} // This would be ignored by Handlebars on the server side allowing it to be used on the client side by Angular.
{{{{/iris_handlebars_ignore}}}}
Iris automatically handles the unescaping of escaped curly brackets so: /{/{/user/}/}
would be ignored by Handlebars on the server side and be translated to {{user}}
when it gets to the client side.
Iris provides a special set of Handlebars helpers, Iris embeds.
{{{iris embed="embedType" param1="value" param2=current.eid}}}
Triple brackets work in the same way as usual in Handlebars. If it's an embed that creates HTML, you'll need to skip the escaping by using three brackets.
Some embeds support block type embedding.
{{#iris embed='embedType' param1='1' as |result|}}
{{#each result}}
{{this.title}}
{{/each}}
{{/iris}}
Note the as
parameter that lists what the embed's return value's variable name within the block will be. This can then be used as normal inside the Handlebars block.
Parameters to an embed can be passed in the following ways.
- A simple text parameter `param1="hello"
- A parameter based on a handlebars variable in the current scope
param2 = current.title
- A JSON parameter
param3='{"type":"hello"}'
- A JSON parameter with a variable inside it (from the current scope) preceded by a dollar sign
param4='{"type":"$current.title"}'
- A single
embedOptions
JSON parameter with all the values you want to passembedOptions='{"title":"hello", "world":true}'
A liveupdate
parameter is available for use on any embed type. Currently only the entity type uses it but any type can easily be converted to use live updating. The parameter sets everything up, all that's needed on the server side is a message to live update an embed template.
Each time a websocket connection is made with the Iris server on a page with embeds marked with the liveupdate parameter, a registration message is sent to the server registering a listener on the embed for that current page view. As soon as the socket disconnects the embed listener is unregistered.
All the currently registered embed listeners are stored in an object called iris.modules.frontend.globals.liveEmbeds
keyed as an object by the embed type.
This contains information about the embed (all its settings) and the authPass and socketID of the user that registered a listener for it.
It has a sendResult()
function that allows you to refresh the embed for the client. If you just want to check the result, or alter it before you send it to the client there's a getResult()
function.
Each embed stores the authPass of the user that registered it so the embed, if reloaded, is reloaded with the same permissions (If these permissions could have changed between the page load and the update event, an authPass can be sent to the sendResult()
update function on each embed listener).
Here's an example that updates all entity embeds across the system for every connected client:
var updateEmbeds = function () {
if (iris.modules.frontend.globals.liveEmbeds.entity) {
Object.keys(iris.modules.frontend.globals.liveEmbeds.entity).forEach(function (embed) {
iris.modules.frontend.globals.liveEmbeds.entity[embed].sendResult();
})
}
};
Forms need a formID parameter. Any other parameters will be passed into the form render hooks.
{{{iris embed="form" formID="login"}}}
Tags have a name
parameter for the tag container name and an exclude parameter that takes a JSON array of tags to exclude from the container.
{{{iris embed='tags' name='headTags' exclude='["socket.io"]'}}}
A user's messages can be shown with:
{{{iris embed="messages"}}}
Menus take a menu
parameter and an optional template
parameter that can override the menu template.
{{{iris embed='menu' menu="admin_toolbar"}}}
Template embeds simply take the template lookup you want to use to embed a template.
{{{iris embed='template' template="sidebar"}}}
Region embeds take a single region
parameter.
{{{iris embed="region" region="sidebar"}}}
Entity embeds are used as block embeds. They take the same parameters as an entity fetch hook on the server side.
Additionally you can use loadscripts=true
to load the fetched entity into a live-updating client side entity database for use with systems such as Angular.js . If this is used you can use the variableName
parameter to store a friendly name for the clientside database collection.
As mentioned above liveupdate=true
will make the embed's contents automatically update if entity content changes in the database.
{{#iris embed='entity' variableName='myEntities' loadscripts=true liveupdate=true entities='["comment"]' queries='[{"field":"title","operator":"contains","value":"hello"}]' as |list|}}
{{#each list}}
{{this.title}}
{{/each}}
{{/iris}}
When the template system comes across an Iris embed called test
for example it runs the hook_frontend_embed__test
hook.
This hook is passed any parameters in thisHook.context.embedOptions
. It should finish with an object like:
thisHook.pass({
content: "hello world", // the text/HTML content that should be put in if used as an inline embed
variables: [list1,list2], // an array of variables to pass through if it is used as a block embed
blockHeader: "", // a text/html header if used as a block embed
blockFooter: "" // A text/html footer if used as a block embed
});
The Iris frontend module's parseTemplateFile function can be used to run a custom module page through the Iris theme system.
iris.modules.frontend.globals.parseTemplateFile takes the following parameters in order.
- An array of template suggestions to look for (page, helloworld) would look for page__helloworld.html, then page.html
- An array of HTML wrapper template suggestions to look for
- An object to be passed to the handlebars templating engine which can then be used on the HTML page templates.
- An authPass with which to run the page.
This returns a JavaScript promise.
Here's an example from the regions page in the admin system.
iris.route.get("/admin/regions", function (req, res) {
iris.modules.frontend.globals.parseTemplateFile(["admin_regions"], ['admin_wrapper'], {
blocks: iris.modules.blocks.globals.blocks,
}, req.authPass, req).then(function (success) {
res.send(success)
}, function (fail) {
iris.modules.frontend.globals.displayErrorPage(500, req, res);
iris.log("error", e);
});
})
hook_frontend_template_parse
runs every time a template is processed by the system. This may be run multiple times if a template is requested that has embeds on it.
It can be used to attach additional variables to be used in the template depending on context.
iris.modules.mymodule.registerHook("hook_frontend_template_parse", function(thisHook, data){
//data.variables contains the current variables available to Handlebars. You can add and edit this.
thisHook.pass(data);
})
- Setting up an Iris site
- Folder structure
- Module system
- Restarting the server after code changes
- Hook system
- Entity system
- Theme and template system
- Form system
- Text filters
- Message system
- Routing system
- Sessions, authentication, user and permission systems
- Configuration system
- Menu system
- Translation system
- Triggers
- Websocket system
- Logs
- Adding tags (meta, css, javascript) to templates dynamically
- Block and region system