Skip to content

Theme and template system

Filip Hnízdo edited this page Sep 8, 2016 · 13 revisions

Overview

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.

Setting the active theme

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.

The .iris.theme info file

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"]
}

The theme folder

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 by npm
  • /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!

Structure of a theme

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.

Template lookups, naming and overrides

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:

Template naming

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.

Where Iris looks for templates.

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.

Typical templates

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.

Templating

Handlebars

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.

The {{current}} variable

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.

Seeing available Handlebars variables

An quick way of seeing which Handlebars variables are available for you to use the following Handlebars snippet:

{{#each this}}
{{@key}}
{{/each}}

Extending handlebars

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);

})

Using other templating languages

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}}}}

Manually escaping curly brackets

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 embed codes

Iris provides a special set of Handlebars helpers, Iris embeds.

Inline 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.

Block embeds

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

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 pass embedOptions='{"title":"hello", "world":true}'

The special liveupdate parameter

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.

Liveupdate API on the server side

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.

Permissions

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).

Example update message function

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();

    })

  }

};

Examples for current embed types

Forms

Forms need a formID parameter. Any other parameters will be passed into the form render hooks.

{{{iris embed="form" formID="login"}}}

Tags

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"]'}}}

Messages

A user's messages can be shown with:


{{{iris embed="messages"}}}

Menu

Menus take a menu parameter and an optional template parameter that can override the menu template.

{{{iris embed='menu' menu="admin_toolbar"}}}

Template

Template embeds simply take the template lookup you want to use to embed a template.

{{{iris embed='template' template="sidebar"}}}

Region

Region embeds take a single region parameter.

{{{iris embed="region" region="sidebar"}}}

Entity

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}}

Creating your own embed code types using hook_frontend_embed__

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
      });

Parsing a template on the server side with iris.modules.frontend.globals.parseTemplateFile

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);

  });

})

Adding items to a templates Handlebars variables using hook_frontend_template_parse

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);

})
Clone this wiki locally