A bit of Mustache
Introduction
In ‘Output three ways’, December 2018, I described how Moodle outputs the web page. The article is technically focussed, but what about the ‘why’? Why do technologies like this exist? In this article I will describe my thoughts and understanding on why Mustache is used in Moodle.
Disclaimers
I am independent from organisations mentioned and am in no way writing for or endorsed by them.
Names / logos can be trademarks of their respective owners. Please review their websites for details.
The information presented in this article is written according to my own understanding, there could be technical inaccuracies, so please do undertake your own research.
Note: Featured image is my own moustache, please don’t use without my permission.
References
- HTML – en.wikipedia.org/wiki/HTML and html.spec.whatwg.org
- Moodle Templates (Mustache) – docs.moodle.org/dev/Templates
- Mustache – mustache.github.io
- PHP – en.wikipedia.org/wiki/PHP and www.php.net
- Stencil – en.wikipedia.org/wiki/Stencil
The why
To understand the ‘why’ we must first understand the problem, Mustache was first introduced in Moodle 2.9 (docs.moodle.org/dev/Templates), therefore if we look at the ‘Clean’ theme in Moodle 2.8, which we can then compare with the ‘Classic’ theme in 3.11 later (as one sort of evolved from the other) then we see that the construction of the output of the two column page layout ‘columns2.php’ looks like this:
// Get the HTML for the settings bits. $html = theme_clean_get_html_for_settings($OUTPUT, $PAGE); $left = (!right_to_left()); // To know if to add 'pull-right' and 'desktop-first-column' classes in the layout for LTR. echo $OUTPUT->doctype() ?> <html <?php echo $OUTPUT->htmlattributes(); ?>> <head> <title><?php echo $OUTPUT->page_title(); ?></title> <link rel="shortcut icon" href="<?php echo $OUTPUT->favicon(); ?>" /> <?php echo $OUTPUT->standard_head_html() ?> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body <?php echo $OUTPUT->body_attributes('two-column'); ?>> <?php echo $OUTPUT->standard_top_of_body_html() ?> <header role="banner" class="navbar navbar-fixed-top<?php echo $html->navbarclass ?> moodle-has-zindex"> <nav role="navigation" class="navbar-inner"> <div class="container-fluid"> <a class="brand" href="<?php echo $CFG->wwwroot;?>"><?php echo format_string($SITE->shortname, true, array('context' => context_course::instance(SITEID))); ?></a> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <?php echo $OUTPUT->user_menu(); ?> <div class="nav-collapse collapse"> <?php echo $OUTPUT->custom_menu(); ?> <ul class="nav pull-right"> <li><?php echo $OUTPUT->page_heading_menu(); ?></li> </ul> </div> </div> </nav> </header> <div id="page" class="container-fluid"> <header id="page-header" class="clearfix"> <?php echo $html->heading; ?> <div id="page-navbar" class="clearfix"> <nav class="breadcrumb-nav"><?php echo $OUTPUT->navbar(); ?></nav> <div class="breadcrumb-button"><?php echo $OUTPUT->page_heading_button(); ?></div> </div> <div id="course-header"> <?php echo $OUTPUT->course_header(); ?> </div> </header> <div id="page-content" class="row-fluid"> <section id="region-main" class="span9<?php if ($left) { echo ' pull-right'; } ?>"> <?php echo $OUTPUT->course_content_header(); echo $OUTPUT->main_content(); echo $OUTPUT->course_content_footer(); ?> </section> <?php $classextra = ''; if ($left) { $classextra = ' desktop-first-column'; } echo $OUTPUT->blocks('side-pre', 'span3'.$classextra); ?> </div> <footer id="page-footer"> <div id="course-footer"><?php echo $OUTPUT->course_footer(); ?></div> <p class="helplink"><?php echo $OUTPUT->page_doc_link(); ?></p> <?php echo $html->footnote; echo $OUTPUT->login_info(); echo $OUTPUT->home_link(); echo $OUTPUT->standard_footer_html(); ?> </footer> <?php echo $OUTPUT->standard_end_of_body_html() ?> </div> </body> </html>
Its a combination of HTML and PHP, with the PHP being injected into the markup via a special ‘tag’ ‘’ where the code is in the place of the ‘attributes’ of the tag itself. PHP is ‘pre-processed’, so the PHP file that we see above is first ‘parsed’ through one program to process the PHP to give output that is HTML that the browser (if correctly written) will understand. One key element to note here is that we have the entire page, there is no inclusion of other ‘components’ that could be reused, like the ‘header’ on other layouts such as one and three columns, even though there could be.
If we look closely we’ll see that the PHP is mostly using the ‘echo’ command to output the results of the methods to the ‘output stream’ of the page. The logic is combined with the data. What we have in essence is a mashing of two different but connected forms. And this is the issue, readability. Can we easily tell what the output and structure of the page will be if we understand HTML in general? What if it gets even more complex? This is where templates come in to attempt to solve this problem.
Templates, the solution?
Templates like stencils (en.wikipedia.org/wiki/Stencil) you may have used when growing up, are a means of repeating the same thing over again with the same framework but with the capability of differences between each instance. Therefore we have a similarity between the concept of a template and the fact that we output a web page from Moodle with the same structure but different data depending on the interactions of the given user at the time. We can’t have a ‘static’ web page that does not change as we as users are interacting with the eLearning tool that Moodle is to provide us with our specific experience.
Moodle uses Mustache as the implementation of templating. The web page, mustache.github.io, states that it is ‘logic less’, however you can have simple ‘if true then’ and ‘if not true then’ statements to include / exclude the contained markup / data.
If we now look at the implementation of the ‘column’ layout in Moodle 3.11’s Classic theme, where it will cope with one, two and three columns ‘columns.mustache’:
{{> theme_boost/head }} <body {{{ bodyattributes }}}> <div id="page-wrapper" class="d-print-block"> {{{ output.standard_top_of_body_html }}} {{>theme_classic/navbar}} <div id="page" class="container-fluid d-print-block"> {{{ output.full_header }}} <div id="page-content" class="row {{#haspreblocks}} blocks-pre {{/haspreblocks}} {{#haspostblocks}} blocks-post {{/haspostblocks}} d-print-block"> <div id="region-main-box" class="region-main"> {{#hasregionmainsettingsmenu}} <div id="region-main-settings-menu" class="d-print-none {{#hasblocks}}has-blocks{{/hasblocks}}"> <div> {{{ output.region_main_settings_menu }}} </div> </div> {{/hasregionmainsettingsmenu}} <section id="region-main" class="region-main-content" aria-label="{{#str}}content{{/str}}"> {{#hasregionmainsettingsmenu}} <div class="region_main_settings_menu_proxy"></div> {{/hasregionmainsettingsmenu}} {{{ output.course_content_header }}} {{{ output.main_content }}} {{{ output.activity_navigation }}} {{{ output.course_content_footer }}} </section> </div> <div class="columnleft blockcolumn {{#haspreblocks}} has-blocks {{/haspreblocks}}"> <section data-region="blocks-column" class="d-print-none" aria-label="{{#str}}blocks{{/str}}"> {{{ sidepreblocks }}} </section> </div> <div class="columnright blockcolumn {{#haspostblocks}} has-blocks {{/haspostblocks}}"> <section data-region="blocks-column" class="d-print-none" aria-label="{{#str}}blocks{{/str}}"> {{{ sidepostblocks }}} </section> </div> </div> </div> {{{ output.standard_after_main_region_html }}} {{> theme_boost/footer }} </div> </body> </html> {{#js}} M.util.js_pending('theme_boost/loader'); require(['theme_boost/loader'], function() { M.util.js_complete('theme_boost/loader'); }); {{/js}}
Then we will see that the page is now easier to read as most of the content is the actual static markup that does not change. The curly bracket ‘{}’ notation is being used as ‘placeholders’ for the data that changes on the page. The ‘{{# thing}}’ notation is the ‘if true then’ syntax, for example if ‘haspreblocks’ is true in ‘{{#haspreblocks}} has-blocks {{/haspreblocks}}’ then the ‘has-blocks’ class will be added to the ‘class’ attribute of the ‘div’ tag to which it belongs. There are also helpers, such as ‘{{#str}}’ and ‘{{#js}}’ to signify Moodle strings and JavaScript respectively. These all help to minimise the impact of the variable nature of the logic on the page.
But there is something missing here! The data! That is kept in a separate PHP file ‘columns.php’:
defined('MOODLE_INTERNAL') || die(); $bodyattributes = $OUTPUT->body_attributes(); $blockspre = $OUTPUT->blocks('side-pre'); $blockspost = $OUTPUT->blocks('side-post'); $hassidepre = $PAGE->blocks->region_has_content('side-pre', $OUTPUT); $hassidepost = $PAGE->blocks->region_has_content('side-post', $OUTPUT); $templatecontext = [ 'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]), 'output' => $OUTPUT, 'sidepreblocks' => $blockspre, 'sidepostblocks' => $blockspost, 'haspreblocks' => $hassidepre, 'haspostblocks' => $hassidepost, 'bodyattributes' => $bodyattributes ]; echo $OUTPUT->render_from_template('theme_classic/columns', $templatecontext);
Where the real logic and data can be gathered up and then with a single ‘echo’ command, the template used with the ‘data’ being the ‘templatecontext’ to generate and output the page.
But what about inclusion of other reusable components? If we look at ‘columns.mustache’ again then we see that it has a ‘partial’ syntax being ‘{{> file }}’, for example ‘{{>theme_classic/navbar}}’ and ‘{{> theme_boost/footer }}’ where we can ‘pull in’ other templates, even from the parent or another theme. If we look at the ‘footer.mustache’ file in the parent Boost theme:
{{! Page footer. }} <div id="goto-top-link"> {{! go to top is sticky to footer so needs to be sibling }} <a class="btn btn-light" role="button" href="#" aria-label="{{#str}} totop, theme_boost {{/str}}"> {{#pix}} i/up, core{{/pix}} </a> </div> <footer id="page-footer" class="py-3 bg-dark text-light"> <div class="container"> <div id="course-footer">{{{ output.course_footer }}}</div> {{# output.page_doc_link }} <p class="helplink">{{{ output.page_doc_link }}}</p> {{/ output.page_doc_link }} {{{ output.login_info }}} <div class="tool_usertours-resettourcontainer"></div> {{{ output.home_link }}} <nav class="nav navbar-nav d-md-none" aria-label="{{#str}}custommenu, admin{{/str}}"> {{# output.custom_menu_flat }} <ul class="list-unstyled pt-3"> {{> theme_boost/custom_menu_footer }} </ul> {{/ output.custom_menu_flat }} </nav> {{{ output.standard_footer_html }}} {{{ output.standard_end_of_body_html }}} </div> </footer>
Then we see that it defines the markup for the footer, but only that, it is not a complete web page but a part of one, a partial. The advantage of this is that we can have many different whole page layouts and yet define an element of the page only once, thereby getting consistency and a single place (most of the time depending on the supporting logic / data) of update if we later decide to change it.
Conclusion
What do you think? Are Mustache templates an improvement? Please let me know in the comments.
- Debugging SCSS – 16th November 2024
- Settings transfer – 16th October 2024
- Added value – 16th September 2024
I can understand how this can be considered an improvement, but it does seem to raise the complexity, at least for non-programmers like myself.
Interesting thought Stuart and indeed it does seem to raise the complexity. But I do think that when layouts get really complex as they have done in Adaptable, Collapsed Topics…, that templating can help to aid maintainability and ease understanding of the output to be generated. As with all things technical though, it is the human element that is the ultimate factor in creating something understandable or a complete and utter mess.