Mustache templates

Mustache templates are the new way of rendering output. They are available from Totara 9 onwards. 

Using Mustache templates

Writing a template

Effectively it is plan HTML with variables injected using {{ }} (AKA mustaches)

An example would be:

<span class="name">{{name}}</span>

If the name was Bob, it would output:

<span class="name">Bob</span>

Formatted HTML

In a lot of places, Totara php functions will return a nicely formatted string containing html tags. Using the example below will display these as plain text:

{{ variablename }}

 For example using the input below:

<a href="blah">blah</a>

 would return the HTML:

&gt;a href=&quote;blah&quote;&lt;blah&gt;/a&lt;

To get around this, use (but beware of XSS attacks):

{{{ variablename }}}

Classes (or key => variable array's)

To display attributes of a class, there are two possible notations, an "in" notation using '#' & '/' and a '.' notation. 

Below is an example of an "in" notation using '#' & '/':

{{#user}}
    {{firstname}}
    {{lastname}}
{{/user}} 

Here is an example of a '.' notation:

{{user.firstname}}
{{user.lastname}}

Loops

For example, if the supplied data has an array called names, it can be looped through in the following manor:

<ul id="users">
    {{#names}}<li class="name">{{.}}</li>{{/names}}
</ul>

If the array names has no elements, then that code snippet will return this invalid HTML:

<ul id="users">
</ul>

One of the limitations of the PHP Mustache implementation is that there is no equivalent for JavaScript arrays' .length property which can be used within templates to check if an array has items or not for situations like the above. It is possible to use an obscure <array>.0 syntax in PHP and JS however its intention is not clear. Therefore in PHP and JS the agreed standard in Totara is to add a variable at the same depth as the array in the template context using the naming convention <arrayname>_has_items which must contain a boolean (opposed to a count) e.g.

{{#names_has_items}}
<ul id="users">
   {{#names}}<li class="name">{{.}}</li>{{/names}}
</ul>
{{/names_has_items}}

Which will output nothing if there are no users, and the following code if names contain Bob and Mary:

<ul id="users">
    <li class="name">Bob</li>
    <li class="name">Mary</li>
</ul>

This approach can also be used with the inverse operator ^ to provide output when the array does not contain items e.g.

{{^names_has_items}}
<p>There are no items.</p>
{{/names_has_items}}

Where do they get stored?

In the templates directory within the plugin. They can also be overwritten in a theme by placing them in the <themename>/templates/<pluginname> directory.

Calling from PHP

This can be done simply by calling:

$OUTPUT->render_from_template('templatename', $data);

Where:

  • templatename is the directory/<template filename>
  • $data is a stdClass with the data to display

For example, if your template is located in user/templates/course_participants.mustache, then the call would be:

$OUTPUT->render_from_template('user/course_participants', $data);

Converting a renderer to a mustache template

The traditional Moodle renderer has been abused and generally contains too much logic. Ideally a renderer should (and Mustache will enforce this):

  • Only have one argument (which may be a class or array). If the object needs to be displayed in a different manor, it should call a different renderer.
  • Only have conditional statements that check if a variable is there or not, or alter the appearance based on what a value is (eg. add a CSS class, and/or display "fail" if a score is below 50%). ALL permissions checks etc. should be done before the renderer is called (and remove data.
  • Only call other renderers, or renderables. All data should be passed in via the 1 argument.

Once the renderer has be cleaned up, then it is merely a matter of translating the output of the PHP to Mustache, and instead of calling render, making a call to $OUTPUT->render_from_template().

Calling from Javascript

They are best loaded using the new javascript format using requireJS (for more documentation see JavaScript & AMD modules).

The dependency that is required is 'core/templates', and then a call can be made to renderTemplate('templatename', data). An example of this is:

require(['core/templates', 'core/notification'], function(templates, notification) {
    var context = { name: 'Tweety bird', intelligence: 2 };
    templates.renderTemplate('block_looneytunes/profile', context)
        .done(doneCallback)
        .fail(notification.exception);
});

Creating Mustache template context examples

In order for a template to provide a rendered example usage in the Template Library Site administration > Development > Template library template files must provide a JSON encoded object (see upstream documentation) which is passed to the rendering function. While this works nicely for simple templates there are a number of problems with this approach when it comes to templates which recieve pre-rendered HTML such as portability of image paths and potential for example interfaces to act on live data. A utility method has been provided to automate removal of the issues found to date and standardise example formatting. Template examples should still be checked manually for potential issues / edge cases yet to be identified.

The utility class is located in admin/tool/templatelibrary/classes/example_data_formatter.php and may be inserted into code just before a call to render_from_template() to output an example. This must be removed before committing the patch and is intended for development purposes only. Example usage:

// In some renderer method where $templatecontext
// has properties containing pre-rendered HTML.

// DEBUG: !! Remove these next 2 lines after copying the output in the browser !!
echo tool_templatelibrary\example_data_formatter::to_json($templatecontext);
die();

return $this->render_from_template('local_myplugin/templatename', $templatecontext);

Helpers

Pictures

This can be used with the following syntax:

{{#pix}} image name, component, alt_identifier, alt_component {{/pix}}

This will display an image (or flex icon if there is a mapping for it). Where dynamic uses of pictures are required, we recommend using dynamic partial templates.

We do not recommend using export_for_pix.

Totara 9 - Totara 11 (before Evergreen 2018-05-14, deprecated in Totara 12)

This can be used with the following syntax:

{{#pix}} image name, component, alt/json blob {{/pix}}

This will display an image (or flex icon if there is a mapping for it). Please note that when using pix_icon::export_for_pix(), it outputs a json blob under the variable "title" (as this is how Moodle created it).

Strings

This can be used with the following syntax:

{{#str}} string name, component, a_identifier, a_component{{/str}}

Where more complex strings are required, we recommend pre-populating a context variable and feeding it through to mustache.

Totara 9 - Totara 11 (before Evergreen 2018-05-14, deprecated in Totara 12)

This can be used with the following syntax:

{{#str}} string name, component, $a variable{{/str}}

This will display the translated string (substituting the $a variable as required).

Flex icons

This can be used with the following syntax:

{{#flex_icon}} name, alt_identifier, alt_component, css_classes {{/flex_icon}}

Where dynamic uses of flex icons are required, we recommend using dynamic partial templates.

Totara 9 - Totara 11 (before Evergreen 2018-05-14, deprecated in Totara 12)

This can be used with the following syntax:

{{#flex_icon}} name, alt/json blob{{/flex_icon}}

This will display a font icon.

Quotes

This can be used with the following syntax:

{{#quote}} some text {{/quote}}

This will put the text in quotes and sanitise quotes correctly.

JavaScript

This can be used with the following syntax:

{{#js}} code {{/js}}

This is preferred as compared to placing JavaScript inside script tags as it will be executed by the templates library (if the template is rendered by JavaScript) or placed at the bottom of the page (when rendered via PHP)

Troubleshooting

Inheritance of context from higher levels

Given the following context data and templates:

$contextdata = [
  'sectionname' => 'My section',
  'classes' => 'sectionclass',
  'items' => [
      [
        'itemname' => 'Item 1',
        'classes' => 'itemclass'
      ],
      [
        'itemname' => 'Item 2'
      ]
   ]
];
// templates/section.mustache
<div class="{{ classes }}">
{{ sectionname }}
{{#items}}
  {{> item }}
{{/items}}
</div>
// templates/item.mustache
<div class="{{ classes }}">
{{ itemname }}
</div>

The output would be:

<div class="sectionclass">
My section
<div class="itemclass">
Item 1
</div>
<div class="sectionclass">
Item 2
</div>
</div>

The issue here is that the second item has the class 'sectionclass' applied, whereas you would perhaps expect it to have an empty class. What's happening is that when mustache fails to find the 'classes' property in the item data, it continues to hunt up the context tree for other references. In this case there is another property further up the context tree with the same name, so it takes the value from there.

To avoid this, you should explicitly provide values for all properties, rather than relying on the defaults. So in this case change the context data will solve the problem, e.g.

$contextdata = [
  'sectionname' => 'My section',
  'classes' => 'sectionclass',
  'items' => [
      [
        'itemname' => 'Item 1',
        'classes' => 'itemclass'
      ],
      [
        'itemname' => 'Item 2',
        'classes' => ''
      ]
   ]
];


Handling of empty values

Note that mustache uses PHP's == operator to compare context data values. If they evaluate to FALSE, then they are treated the same as if they are empty. For example:

// templates/section.mustache
<div>
{{#items}}
  {{> item }}
{{/items}}
</div>
// templates/item.mustache
<div>
{{ itemname }}
{{#iteminfo}}iteminfo is: {{.}}{{/iteminfo}}
{{^iteminfo}}iteminfo is not set{{/iteminfo}}
</div>
$contextdata  = [
    'iteminfo' => 'PARENT',
    'items' => [
        [
            'itemname' => 'Item with normal string',
            'iteminfo' => 'Normal string'
        ],
        [
            'itemname' => 'Item with number zero',
            'iteminfo' => '0'
        ],
        [
            'itemname' => 'Item with empty string',
            'iteminfo' => ''
        ],
        [
            'itemname' => 'Item with empty array',
            'iteminfo' => []   // this is invalid data, but given to show that it impacts empty arrays too
        ],
        [
            'itemname' => 'Item with no iteminfo property'
        ]
    ]
];

Will output:

<div>
  <div>
  Item with normal string
  iteminfo is: Normal string

  </div>
  <div>
  Item with number zero

  iteminfo is not set
  </div>
  <div>
  Item with empty string

  iteminfo is not set
  </div>
  <div>
  Item with empty array

  iteminfo is not set
  </div>
  <div>
  Item with no iteminfo property
  iteminfo is: PARENT
  </div>
</div>

Of particular note here is the value of zero, which you may well want to display the number zero instead of an empty string. See 

Error rendering macro 'jira' : Unable to locate Jira server for this macro. It may be due to Application Link configuration.
 for an example where this caught us out.

To work around this you need to create a separate property in the context data 'hasInfo' which has a boolean value, then you can directly display iteminfo without the check:

$contextdata  = [
    'iteminfo' => 'PARENT',
    'items' => [
        [
            'itemname' => 'Item with normal string',
            'iteminfo' => 'Normal string',
            'hasinfo' => true
        ],
        [
            'itemname' => 'Item with number zero',
            'iteminfo' => '0',
            'hasinfo' => true
        ],
        [
            'itemname' => 'Item with empty string',
            'iteminfo' => '',
            'hasinfo' => true
        ],
        [
            'itemname' => 'Item with empty array',
            'iteminfo' => [],
            'hasinfo' => true
        ],
        [
            'itemname' => 'Item with no iteminfo property',
            'hasinfo' => false
        ]
    ]
];
// templates/item.mustache
<div>
{{ itemname }}
{{#hasinfo}}iteminfo is: {{iteminfo}}{{/hasinfo}}
{{^hasinfo}}iteminfo is not set{{/hasinfo}}
</div>