User:Damyon Wiese/Templates

Jump to: navigation, search

Templates

What is a template?

A template is an alternative to writing blocks of html directly in javascript / php by concatenating strings. The end result is the same, but templates have a number of advantages:

  • It is easier to see the final result of the template because the code for a template is very close to what the final HTML will look like
  • Because the templating language is intentionally limited, it is hard to introduce complex logic into a template. This make it far easier for a theme designer to override a template, without breaking the logic
  • Templates can be rendered from javascript. This allows ajax operations to re-render a portion of the page.

How do I write a template?

Templates are written in a language called "Mustache". Mustache is written as HTML with additional tags used to format the display of the data. Mustache tags are made of 2 opening and closing curly braces "Template:tag". There are a few variations of these tags that behave differently.

  • {{raiden}}
    This is a simple variable substitution. The variable named "variable" will be searched for in the current context (and any parent contexts) and when a value is found, the entire tag will be replaced by the variable (html escaped).
  • {{{galaga}}}
    This is an unescaped variable substitution. Instead of escaping the variable before replacing it in the template, the variable is included raw. This is useful when the variable contains a block of HTML (for example).
  • {{#lemmings}} jump off cliff {{/#lemmings}}
    These are opening and closing section tags. If the lemmings variable exists and evaluates to "not false" value, the variable is pushed on the stack, the contents of the are section parsed and included in the result. If the variable does not exist, or evaluates to false - the section will be skipped. If the variable lemmings evaluates to an array, the section will be repeated for each item in the array with the items of the array on the context. This is how to output a list.
  • {{> pacman }}
    This is a partial. Think of it like an include. Templates can include other templates using this tag.

So - putting this all together:

recipe.mustache

<h3>{{recipename}}</h3>
<p>{{description}}</p>
<h4>Ingredients</h4>
<ol>
{{#ingredients}}
<li>{{.}}</li>
{{/ingredients}}
</ol>
<h4>Steps</h4>
<ol>
{{#steps}}
<li>{{{.}}}</li>
{{/steps}}
</ol>
{{ > ratethisrecipe }}

When given this data:

{
  recipename: "Cheese sandwich",
  description: "Who doesn't like a good cheese sandwich?",
  ingredients: ["bread", "cheese", "butter"],
  steps: ["<p>Step 1 is to spread the butter on the bread</p>", "<p>Step 2 is to put the cheese &quot;in&quot; the bread (not on top, or underneath)</p>"]
}

Gives this: 😋

More info - there are much clearer explanations of templates on the Mustache website. Try reading those pages "before" posting on stack overflow :) .

Where do I put my templates?

Templates go in the <componentdir>/templates folder and must have a .mustache file extension. When loading templates the template name is <componentname>/<filename> (no file extension).

So "mod_lesson/timer" would load the template at mod/lesson/templates/timer.mustache.

Note: Do not try and put your templates in sub folders under the "/templates" directory. This is not supported and will not work.

How do I call a template from javascript?

Rendering a template from javascript is fairly easy. There is a new AMD module that can load/cache and render a template for you.

// This is AMD code for loading the "core/templates" module. see [User:Damyon_Wiese/Javascript_Modules Javascript Modules].
require(['core/templates'], function(templates) {
 
    // This will be the context for our template. So {{name}} in the template will resolve to "Tweety bird".
    var context = { name: 'Tweety bird', intelligence: 2 };
 
    // This will call the function to load and render our template. 
    var promise = templates.render('block_looneytunes/profile', context);
 
    // The promise object returned by this function means "I've considered your request and will finish it later - I PROMISE!"
 
    // How we deal with promise objects is by adding callbacks.
    promise.done(function(source, javascript) {
        // Here eventually I have my compiled template, and any javascript that it generated.
    });
 
    // Sometimes things fail
    promise.fail(function(ex) {
        // Deal with this exception (I recommend core/notify exception function for this).
    });
});


Under the hood, this did many clever things for us. It loaded the template via an ajax call if it was not cached. It found any missing lang strings in the template and loaded them in a single ajax request, it split the JS from the HTML and returned us both in easy to use way. Read on for how to nicely deal with the javascript parameter.

Note: with some nice chaining and sugar, we can shorten the above example quite a bit:

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

What if a template contains javascript?

Sometimes a template requires that some JS be run when it is added to the page in order to give it more features. In the template we can include blocks of javascript, but we should use a special section tag that has a "helper" method registered to handle javascript carefully.

Example profile.mustache

<div id="profile">
<p>Name: {{name}}</p>
<p>Intelligence: {{intelligence}}</p>
</div>
{{#js}}
require('jquery', function($) {
    // Effects! Can we have "blink"?
    $('#profile').slideDown();
});
{{/js}}


If this template is rendered by PHP, the javascript is separated from the HTML, and is appended to a special section in the footer of the page "after" requirejs has loaded. This provides the optimal page loading speed. If the template is rendered by javascript, the javascript source will be passed to the "done" handler from the promise. Then, when the "done" handler has added the template to the DOM, it can call

templates.runTemplateJS(javascript);

which will create a new script tag and append it to the page head.

What other helpers can I use?

There is a string helper for loading language strings.

Example:

{{#str}} iscool, mod_cool, David Beckham {{/str}}

The first 2 words are the string id and the component name, the rest of the section is the content for the $a variable. So this example would call get_string('iscool', 'mod_cool', 'David Beckham');

Variables are allowed in the text for the $a param. Example:

{{#str}} iscool, mod_cool, {{name}} {{/str}}

For strings that accept complex $a params, you can use a json object here instead:

{{#str}} iscool, mod_cool, { firstname: 'David', lastname: 'Beckham'}{{/str}}

There is a pix icon helper for generating pix icon tags. Example:

{{#pix}} t/edit core Edit David Beckham {{/pix}}

The first 2 words are the string id and the component name, the rest is the alt text for the image.

How do I call a template from php?

The templates in php are attached to the renderers. There is a renderer method "render_from_template($templatename, $context)" that does the trick.

How do templates work with renderers?

Extra care must be taken to ensure that the data passed to the context parameter is useful to the templating language. The template language cannot:

  • Call functions
  • Perform any boolean logic
  • Render renderables
  • Do capability checks
  • Make DB queries

So - I have "some" data in my renderable and some logic and html generation in my render method for that renderable - how do I refactor this to use a template?

The first thing to note, is that you don't have to use a template if you don't want to. It just means that themers will still have to override your render method, instead of just overriding the template. But if you DO want to use a template, you will earn "cred" with themers, and you will be able to re-render parts of your interface from javascript in response to ajax requests without reloading the whole page (that's cool).

There is a simple pattern to use to hook a template into a render method. If you make your renderable implement templatable as well as renderable - it will have to implement a new method "export_for_template(renderer_base $output)". This method takes the data stored in the renderable and "flattens it" so it can be used in a template. If there is some nested data in the renderable (like other renderables) and they do not support templates, they can be "rendered" into the flat data structure using the renderer parameter. It should return an stdClass with properties that are only made of simple types: int, string, bool, float, stdClass or arrays of these types. Then the render method can updated to export the data and render it with the template.

In the renderable:

/**
     * Export this data so it can be used as the context for a mustache template.
     *
     * @return stdClass
     */
    public function export_for_template(renderer_base $output) {
        $data = new stdClass();
        $data->canmanage = $this->canmanage;
        $data->things = array();
        foreach ($this->things as $thing) {
            $data->things[] = $thing->to_record();
        }
        $data->navigation = array();
        foreach ($this->navigation as $button) {
            $data->navigation[] = $output->render($button);
        }
 
        return $data;
    }

In the renderer class:

/**
     * Defer to template.
     *
     * @param mywidget $widget
     *
     * @return string html for the page
     */
    render(mywidget $widget) {
        $data = $widget->export_for_template($this);
        retrn self::render_from_template('mywidget', $data);
    }

How to I override a template in my theme?

Templates can be overridden a bit easier than overriding a renderer. First - find the template that you want to change. E.g. "mod/wiki/templates/ratingui.mustache". Now, create a sub-folder under your themes "templates" directory with the component name of the plugin you are overriding. E.g "theme/timtam/templates/mod_wiki". Finally, copy the ratingui.mustache file into the newly created "theme/timtam/templates/mod_wiki" and edit it. You should see your changes immediately if theme designer mode is on. Note: templates are cached just like CSS, so if you are not using theme designer mode you will need to purge all caches to see the latest version of an edited template.

Should I document my templates?

Yes!!!! Theme designers need to know the limits of what they can expect to change without breaking anything. As a further benefit - your beautiful new template can be displayed in the "Template Library" tool shipped with Moodle. In order to provide nice documentation and examples for the Template Library, you should follow these conventions when documenting your template.

Add a documentation comment to your template

Mustache comments look like this:

  {{! 
   I am a comment.
   I can span multiple lines.
  }}

The template library will look for a mustache comment that contains this special marker as the documentation to display, and the source of an example context.

@template component/templatename

Useful things to include in the documentation for a template

Classes required for JS

This is a list of classes that are used by the javascript for this template. If removing a class from an element in the template will break the javascript, list it here.

Data attributes required for JS

This is a list of data attributes (e.g. data-enhance="true") that are used by the javascript for this template. If removing a data attribute from an element in the template will break the javascript, list it here.

Context variables required for this template

This is a description of the data that may be contained in the context that is passed to the template. Be explicit and document every attribute.

Example context (json)

The Template Library will look for this data in your documentation comment as it allows it to render a "preview" of the template right in the Template Library. This is useful for theme designers to test all the available templates in their new theme to make sure they look nice in a new theme. It is also useful to make sure the template responds to different screen sizes, languages and devices. The format is a json encoded object that is passed directly into the render method for this template.

A full example

lib/templates/pix_icon.mustache

  {{!                                                                                                                                 
    This file is part of Moodle - http://moodle.org/                                                                                
                                                                                                                                    
    Moodle is free software: you can redistribute it and/or modify                                                                  
    it under the terms of the GNU General Public License as published by                                                            
    the Free Software Foundation, either version 3 of the License, or                                                               
    (at your option) any later version.                                                                                             
                                                                                                                                    
    Moodle is distributed in the hope that it will be useful,                                                                       
    but WITHOUT ANY WARRANTY; without even the implied warranty of                                                                  
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                                                   
    GNU General Public License for more details.                                                                                    
                                                                                                                                    
    You should have received a copy of the GNU General Public License                                                               
    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.                                                                 
  }}                                                                                                                                  
  {{!                                                                                                                                 
    @template core/pix_icon                                                                                                         
                                                                                                                                    
    Moodle pix_icon template.                                                                                                       
                                                                                                                                    
    The purpose of this template is to render a pix_icon.                                                                           
                                                                                                                                    
    Classes required for JS:                                                                                                        
    * none                                                                                                                          
                                                                                                                                    
    Data attributes required for JS:                                                                                                
    * none                                                                                                                          
                                                                                                                                    
    Context variables required for this template:                                                                                   
    * attributes Array of name / value pairs.                                                                                       
                                                                                                                                    
    Example context (json):                                                                                                         
    {                                                                                                                               
        "attributes": [                                                                                                             
            { "name": "src", "value": "http://moodle.com/wp-content/themes/moodle/images/logo-hat2.png" },                          
            { "name": "class", "value": "iconsmall" }                                                                               
        ]                                                                                                                           
    }                                                                                                                               
                                                                                                                                    
  }}                                                                                                                                  
  <img {{#attributes}}{{name}}="{{value}}" {{/attributes}}/>