Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Themes overview: Difference between revisions

From MoodleDocs
(22 intermediate revisions by 5 users not shown)
Line 1: Line 1:
{{Template:Themes}}Welcome to the new world of themes in Moodle!
{{Template:Themes}}
Welcome to the new world of themes in Moodle!


A Moodle theme allows the user to change the look and feel of a Moodle site.  Themes can be applied on the site, category, course and activity levels by users with permissions to do so.  Themes can be designed for specific devices such as mobile phones or tablets. This page explains how themes work in Moodle and is intended to help you create or modify most themes for Moodle.
A Moodle theme allows the user to change the look and feel of a Moodle site.  Themes can be applied on the site, category, course and activity levels by users with permissions to do so.  Themes can be designed for specific devices such as mobile phones or tablets. This page explains how themes work in Moodle and is intended to help you create or modify most themes for Moodle.
Line 267: Line 268:
; $THEME->name : The first setting, is the theme's name. This should simply be whatever your theme's name is, most likely whatever you named your theme directory.
; $THEME->name : The first setting, is the theme's name. This should simply be whatever your theme's name is, most likely whatever you named your theme directory.


; $THEME->sheets : An array containing the names of the CSS stylesheets to include for this theme. Boost uses scss instead of css so it doesn't list any files here. Note that it is just the name of the stylesheet and does not contain the directory or the file extension. Moodle assumes that the theme's stylesheets will be located in the styles directory of the theme and have .css as an extension.
; $THEME->sheets : An array containing the names of the CSS stylesheets to include for this theme. Boost uses scss instead of css so it doesn't list any files here. Note that it is just the name of the stylesheet and does not contain the directory or the file extension. Moodle assumes that the theme's stylesheets will be located in the {theme}/'''style''' directory of the theme and have .css as an extension.


; $THEME->editorsheets : An array containing the names of the CSS stylesheets to include for the TinyMCE text editor content area. Boost does not list any stylesheets here so TinyMCE will use plain text styles.
; $THEME->editorsheets : An array containing the names of the CSS stylesheets to include for the TinyMCE text editor content area. Boost does not list any stylesheets here so TinyMCE will use plain text styles.
Line 301: Line 302:
As theme designers, we will only use the first method of introducing CSS: adding rules to a stylesheet file located in the theme's style directory.
As theme designers, we will only use the first method of introducing CSS: adding rules to a stylesheet file located in the theme's style directory.
===Better than CSS===
===Better than CSS===
Browsers understand CSS well - but it is hard to write and maintain. The language does not support inheritance and reuse and does not allow configuration with variables. This is why CSS pre-processors were invented. Moodle supports 2 different types of CSS pre-processors (Less and Sass) but the Sass pre-processor is recommended by far.  
Browsers understand CSS well - but it is hard to write and maintain. The language does not support inheritance and reuse. [https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables Support for variables] exists in more modern browsers only. This is why CSS pre-processors were invented. Moodle supports 2 different types of CSS pre-processors (Less and Sass) but the Sass pre-processor is recommended by far.  


For more information on Less and Sass see:  
For more information on Less and Sass see:  
Line 313: Line 314:


===How to write effective CSS rules within Moodle===
===How to write effective CSS rules within Moodle===
In Moodle 2.0, writing good CSS rules is incredibly important.
Writing good CSS rules is incredibly important.


Due to performance requirements and browser limitations, all of the CSS files are combined into a single CSS file that gets included every time. This means that rules need to be written in such a way as to minimise the chances of a collision leading to unwanted styles being applied. Whilst writing good CSS is something most designers strive for we have implemented several new body classes and prompt developers to use appropriate classnames.
Due to performance requirements and browser limitations, all of the CSS files are combined into a single CSS file that gets included every time. This means that rules need to be written in such a way as to minimise the chances of a collision leading to unwanted styles being applied. Whilst writing good CSS is something most designers strive for we have implemented several new body classes and prompt developers to use appropriate classnames.
Line 358: Line 359:


By making use of body id's and classes and writing selectors to take into account the leading structure you can greatly minimise the chance of a collision both with Moodle now and in the future.
By making use of body id's and classes and writing selectors to take into account the leading structure you can greatly minimise the chance of a collision both with Moodle now and in the future.
It is also important to write as FEW rules as possible. CSS is extremely hard to maintain and lots of CSS is bad for client side performance. Themes based on the Bootstrap CSS framework can achieve most things without writing a single additional CSS rule. Please read [http://v4-alpha.getbootstrap.com/ the Bootstrap documentation] and learn how to use Bootstrap well to avoid adding unnecessary CSS rules for things already provided by the framework.


==Layouts==
==Layouts==
Line 406: Line 409:


For example:
For example:
''theme/themename/config.php''
''theme/boost/config.php''
<code php>
<code php>
'course' => array(
...
    'file' => 'columns2.php',
    'popup' => array(                                                                                                              
    'regions' => array('side-pre'),
        'file' => 'columns1.php',                                                                                                  
    'defaultregion' => 'side-pre',
        'regions' => array(),                                                                                                      
    'options' => array('langmenu' => true)
        'options' => array('nofooter' => true, 'nonavbar' => true),                                                               
),
    ),                              
...
</code>
</code>


This means every page that has pagetype 'course' will be displayed with the 'theme/themename/layout/columns2.php' file and it will have 1 block region named 'side-pre', new blocks will be added to the 'side-pre' region and there are some options that will be available to the page in the global variable "$PAGE->layout_options".
This means every page that has pagetype 'popup' will be displayed with the 'theme/themename/layout/columns1.php' file, it will have no block regions and there are some options that will be available to the page in the global variable "$PAGE->layout_options".


It is possible to implement a layout file directly in php by echoing the HTML for the page, or mixing php tags with HTML - but a better way to create a layout file is to gather all the information required for the layout into a context and render it with a mustache template.  
It is possible to implement a layout file directly in php by echoing the HTML for the page, or mixing php tags with HTML - but a better way to create a layout file is to gather all the information required for the layout into a context and render it with a mustache template.  


[[Templates Read about mustache templates]]
[[Templates| Read about mustache templates]]


Using templates for layout files makes a lot of sense because they are easier to read and maintain than mixing PHP and HTML in the same file.


A simple example of a layout file using a template is at:


''theme/boost/layout/columns1.php''
<code php>
<?php


$bodyattributes = $OUTPUT->body_attributes([]);                                                                                   
                                                                                                                                   
$templatecontext = [                                                                                                               
    'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),       
    'output' => $OUTPUT,                                                                                                           
    'bodyattributes' => $bodyattributes                                                                                           
];                                                                                                                                 
                                                                                                                                   
echo $OUTPUT->render_from_template('theme_boost/columns1', $templatecontext); 
</code>


This example puts some variables into a templatecontext and then calls "render_from_template" to render the mustache template for this layout. The template is located at: "theme/boost/templates/columns1.mustache". It is possible to put PHP classes in the context for the mustache template - and any public properties or methods which accept no arguments will be available to the template. $OUTPUT has several useful public methods which accept no arguments and is a valuable class when creating a layout template in mustache.


The mustache template for this layout is shown here:


''theme/boost/templates/columns1.mustache''
<code handlebars>
{{{ output.doctype }}}                                                                                                             
<html {{{ output.htmlattributes }}}>                                                                                               
<head>                                                                                                                             
    <title>{{{ output.page_title }}}</title>                                                                                       
    <link rel="shortcut icon" href="{{{ output.favicon }}}" />                                                                     
    {{{ output.standard_head_html }}}                                                                                             
    <meta name="viewport" content="width=device-width, initial-scale=1.0">                                                         
</head>                                                                                                                           
                                                                                                                                   
<body {{{ bodyattributes }}}>                                                                                                     
                                                                                                                                   
<div id="page-wrapper">                                                                                                           
                                                                                                                                   
    {{{ output.standard_top_of_body_html }}}                                                                                       
                                                                                                                                   
    <div id="page" class="container-fluid">                                                                                       
        <div id="page-content" class="row">                                                                                       
            <div id="region-main-box" class="col-xs-12">                                                                           
                <section id="region-main">                                                                                         
                    <div class="card card-block">                                                                                 
                    {{{ output.course_content_header }}}                                                                           
                    {{{ output.main_content }}}                                                                                   
                    {{{ output.course_content_footer }}}                                                                           
                    </div>                                                                                                         
                </section>                                                                                                         
            </div>                                                                                                                 
        </div>                                                                                                                     
    </div>                                                                                                                         
</div>                                                                                                                             
{{{ output.standard_end_of_body_html }}}                                                                                           
</body>                                                                                                                           
</html>                                                                                                                           
{{#js}}                                                                                                                           
require(['theme_boost/loader']);                                                                                                   
{{/js}}
</code>


Explaining each line of this template will answer a lot of questions. This example contains only the very minimal required functions to generate a valid layout. You should consider all of the sections below as required in every layout file (although any of the HTML tags can and should be altered).


A layout file is a file that contains the core HTML structure for a layout including the header, footer, content and block regions. Of course it is not all HTML, there are bits of HTML and content that Moodle needs to put into the page, within each layout file this will be done by a couple of simple PHP calls to get bits and pieces including content.
<code handlebars>
{{{ output.doctype }}}                                                                                                             
</code>
 
This is an example of calling a function on $OUTPUT in php. Because there is a public method on the output class named "doctype" which accepts no arguments - mustache will call it and return the output. We call a function to generate the doctype tag because calling this function returns us the correct HTML for the document type for this theme AND it sets a different content type header (including the charset) depending on the doc type for the theme. Setting a correct charset in every page is important to prevent a class of XSS attacks.  


Before learning more it is good to understand the two primary objects that will be used in your layout files: $OUTPUT and $PAGE.
<code handlebars>
<html {{{ output.htmlattributes }}}>                                                                                               
</code>


'''$OUTPUT''' is an instance of the <code>core_renderer</code> class which is defined in lib/outputrenderers.php. Each method is clearly documented there, along with which is appropriate for use within the layout files.
Now we have returned our root tag - the html tag. We included a set of default attributes for the page by calling the htmlattributes function of our output class. This includes the correct language attribute for the entire page and can include an xml namespace for XHTML documents.  


'''$PAGE''' is an instance of the <code>moodle_page</code> class defined in lib/pagelib.php. Most of the things you will want to use are the properties that are all documented at the top of the file. If you are not familiar with PHP properties, you access them like $PAGE->activityname, just like fields of an ordinary PHP object. (However, behind the scenes the value you get is produced by calling a function. Also, you cannot change these values, they are '''read-only'''. However, you don't need to understand all that if you are just using these properties in your theme.)
<code handlebars>
<head>                                                                                                                             
    <title>{{{ output.page_title }}}</title>                                                                                      
</code>


The following is a very simple layout file to illustrate the different bits that make it up:
Now we have started the head section of our document and set the title for the page. Notice the title is already escaped by the output class so we are using triple mustache tags "{{{" to avoid double escaping.
<code php>
 
<?php echo $OUTPUT->doctype() ?>
<code handlebars>
<html <?php echo $OUTPUT->htmlattributes() ?>>
     <link rel="shortcut icon" href="{{{ output.favicon }}}" />                                                                    
<head>
</code>
    <title><?php echo $PAGE->title ?></title>
 
    <?php echo $OUTPUT->standard_head_html() ?>
We call a function to get the url to the favicon. The favicon is a file in the theme pix directory and it is served through the "theme/image.php" file which adds special caching headers for images.
</head>
 
<body id="<?php p($PAGE->bodyid) ?>" class="<?php p($PAGE->bodyclasses) ?>">
<code handlebars>
<?php echo $OUTPUT->standard_top_of_body_html() ?>
    {{{ output.standard_head_html }}}                                                                                             
<table id="page">
</code>
     <tr>
 
        <td colspan="3">
The standard head html function performs a lot of setup that is required for our page. It internally creates the block regions, creates meta tags including keywords for SEO, initialises the common javascript modules, generates links to the style sheets and injects any additional HTML set by the $CFG->additionalhtmlhead setting.
            <h1 class="headermain"><?php echo $PAGE->heading ?></h1>
 
            <div class="headermenu"><?php
<code handlebars>
                if (method_exists($OUTPUT, 'user_menu')) {
     <meta name="viewport" content="width=device-width, initial-scale=1.0">                                                        
                  echo $OUTPUT->user_menu(); // user menu, for Moodle 2.8
</head>                                                                                                                            
                } else {
                                                                                                                                   
                  echo $OUTPUT->login_info(); // login_info, Moodle 2.7 and before
</code>
                }
 
                echo $PAGE->headingmenu;
This viewport meta tag is recommended by bootstrap for "proper viewport rendering and touch zooming".
              ?></div>
 
        </td>
<code handlebars>
    </tr>
<body {{{ bodyattributes }}}>                                                                                                      
    <tr>
</code>
        <td>
 
            <?php echo $OUTPUT->blocks_for_region('side-pre') ?>
The body attributes include the language direction and standard classes for the page.
        </td>
 
        <td>
<code handlebars>
            <?php echo core_renderer::MAIN_CONTENT_TOKEN ?>
                                                                                                                                   
        </td>
<div id="page-wrapper">                                                                                                            
        <td>
                                                                                                                                   
            <?php echo $OUTPUT->blocks_for_region('side-post') ?>
        </td>
    </tr>
     <tr>
        <td colspan="3">
            <p class="helplink"><?php echo page_doc_link(get_string('moodledocslink')) ?></p>
            <?php
            echo $OUTPUT->login_info();
            echo $OUTPUT->home_link();
            echo $OUTPUT->standard_footer_html();
            ?>
        </td>
    </tr>
</table>
<?php echo $OUTPUT->standard_end_of_body_html() ?>
</body>
</html>
</code>
</code>


We assume you know enough HTML to understand the basic structure above, but let's explain the PHP code since that is less obvious.
In the Boost theme we use a page-wrapper div to prevent content from disappearing under the fixed header.
<code php>
 
<?php echo $OUTPUT->doctype() ?>
<code handlebars>
    {{{ output.standard_top_of_body_html }}}                                                                                       
                                                                                                                                   
</code>
</code>
This occurs at the VERY top of the page, it must be the first bit of output and is responsible for adding the (X)HTML document type definition to the page. This of course is determined by the settings of the site and is one of the things that the theme designer has no control over.


<code php>
The standard_top_of_body_html should be included in every layout and includes skip links for accessibility as well as initialising jquery, yui and our own static javascript files.
<html <?php echo $OUTPUT->htmlattributes() ?>>
 
<code handlebars>
    <div id="page" class="container-fluid">                                                                                       
        <div id="page-content" class="row">                                                                                       
            <div id="region-main-box" class="col-xs-12">                                                                          
                <section id="region-main">                                                                                        
                    <div class="card card-block">                                                                                  
</code>
</code>
Here we have started writing the opening html tag and have asked Moodle to give us the HTML attributes that should be applied to it. This again is determined by several settings within the actual HTML install.


<code php>
This is standard HTML tags defining the content region for this page. The classes come from bootstrap 4.
<?php echo $PAGE->title ?>
 
<code handlebars>
                    {{{ output.course_content_header }}}                                                                           
</code>
</code>
This gets us the title for the page.


<code php>
The course content header allows Moodle plugins to inject things in the top of the page. This is used for "notifications" for example (which are the alert boxes you see after submitting a form).
<?php echo $OUTPUT->standard_head_html() ?>
 
<code handlebars>
                    {{{ output.main_content }}}                                                                                   
</code>
</code>
This very important call gets us the standard head HTML that needs to be within the HEAD tag of the page. This is where CSS and JavaScript requirements for the top of the page will be output as well as any special script or style tags.


<code php>
The main content function returns the real content for the page.
<body id="<?php p($PAGE->bodyid); ?>" class="<?php p($PAGE->bodyclasses); ?>">
 
<code handlebars>
                    {{{ output.course_content_footer }}}                                                                           
</code>
</code>
Much like the html tag above we have started writing the body tag and have asked for Moodle to get us the desired ID and classes that should be applied to it.


<code php>
The course content footer is used mainly by course formats to insert things after the main content.
<h1 class="headermain"><?php echo $PAGE->heading; ?></h1>
 
<div class="headermenu"><?php
<code handlebars>
if (method_exists($OUTPUT, 'user_menu')) {
                    </div>                                                                                                        
  echo $OUTPUT->user_menu(); // user menu, for Moodle 2.8 onwards
                </section>                                                                                                        
} else {
            </div>                                                                                                                
  echo $OUTPUT->login_info(); // login_info, Moodle 2.7 and before
        </div>                                                                                                                    
}
    </div>                                                                                                                        
echo $PAGE->headingmenu; ?></div>
</div>                                                                                                                            
</code>
</code>
Here we are creating the header for the page. In this case we want the heading for the page, a user menu (for Moodle 2.8+) or login information (the current users username or a link to log in if they are not logged in), and we want the heading menu if there is one.


<code php>
We close all our open tags...
<?php echo $OUTPUT->blocks_for_region('side-pre') ?>
 
<code handlebars>
{{{ output.standard_end_of_body_html }}}                                                                                           
</code>
</code>
Here we get the HTML to display the blocks that have been added to the page. In this case we have asked for all blocks that have been added to the area labelled ''side-pre''.


<code php>
This function will add all of the javascript that was required while rendering the page. Javascript is added at the end of the document so that it does not block rendering the page.
<?php echo core_renderer::MAIN_CONTENT_TOKEN ?>
<code handlebars>
</body>                                                                                                                            
</html>                                                                                                                            
</code>
</code>
This is one of the most important calls within the file, it determines where the actual content for the page gets inserted.


<code php>
We finish the HTML for our page.
<?php echo $OUTPUT->blocks_for_region('side-post') ?>
<code handlebars>
{{#js}}                                                                                                                           
require(['theme_boost/loader']);                                                                                                   
{{/js}}
</code>
</code>
Here we get the HTML to display the blocks that have been added to the page. In this case we have asked for all blocks that have been added to the area labelled ''side-post''.


This final section is required for bootstrap 4 themes and loads all the Bootstrap 4 Javascript dependencies.
If we had block regions in this layout we would need to insert them in the template. The way we would do this is by getting the HTML for the block region in our layout php file, adding it to the context and then including it in our template.
''theme/boost/layout/columns2.php''
<code php>
<code php>
<?php
...
echo $OUTPUT->login_info();
$blockshtml = $OUTPUT->blocks('side-pre');                                                                                        
echo $OUTPUT->home_link();
$hasblocks = strpos($blockshtml, 'data-block=') !== false;
echo $OUTPUT->standard_footer_html();
...
?>
$templatecontext = [
...
    'sidepreblocks' => $blockshtml,                                                                                               
    'hasblocks' => $hasblocks,
...
];
...
echo $OUTPUT->render_from_template('theme_boost/columns2', $templatecontext);
</code>
</code>
This final bit of code gets the content for the footer of the page. It gets the login information which is the same as in the header, a home link, and the standard footer HTML which like the standard head HTML contains all of the script and style tags required by the page and requested to go in the footer.


'''''Note''''': Within Moodle 2.0 most of the JavaScript for the page will be included in the footer. This greatly helps reduce the loading time of the page.
''theme/boost/templates/columns2.mustache''
<code handlebars>
...
                {{#hasblocks}}                                                                                                     
                <section data-region="blocks-column" class="hidden-print">                                                         
                    {{{ sidepreblocks }}}                                                                                         
                </section>                                                                                                         
                {{/hasblocks}}
...
</code>


When writing layout files think about the different layouts and how the HTML that each makes use of will differ. You will most likely find you do not need a different layout file for each layout, most likely you will be able to reuse the layout files you create across several layouts. You can of course make use of layout options as well to further reduce the number of layout files you need to produce.
When writing layout files think about the different layouts and how the HTML that each makes use of will differ. You will most likely find you do not need a different layout file for each layout, most likely you will be able to reuse the layout files you create across several layouts. You can of course make use of layout options as well to further reduce the number of layout files you need to produce.
Line 559: Line 643:
==Language File==
==Language File==


You need to create a language file for your theme with a few standard strings in it. At a minimum create a file called lang/en/theme_themename.php in your theme folder. For example, the 'standard' theme has a language file called lang/en/theme_standard.php.  
You need to create a language file for your theme with a few standard strings in it. At a minimum create a file called lang/en/theme_themename.php in your theme folder. For example, the 'boost' theme has a language file called lang/en/theme_boost.php.  


You '''must''' define the following lines in your file (example is from standard theme, adapt as required):
You '''must''' define the following lines in your file (example is from Boost theme, adapt as required):


<code php>
<code php>
$string['pluginname'] = 'Standard';
$string['pluginname'] = 'Boost';
$string['region-side-post'] = 'Right';
$string['region-side-pre'] = 'Right';
$string['region-side-pre'] = 'Left';
$string['choosereadme'] = 'Boost is a modern highly-customisable theme. This theme is intended to be used directly, or as a parent theme when creating new themes utilising Bootstrap 4.';
$string['choosereadme'] = 'This theme is a very basic white theme, with a minimum amount of
CSS added to the base theme to make it actually usable.';
</code>
</code>


Line 589: Line 671:
Notice that one image is a JPEG image, and the second is a PNG. Also the second image is in a subdirectory.
Notice that one image is a JPEG image, and the second is a PNG. Also the second image is in a subdirectory.


The following code snippet illustrates how to make use of your images within HTML, such as if you wanted to use them within a layout file.
The following code snippet illustrates how to make use of your images within your layout file so they can be inserted by your layout template.
''theme/yourtheme/layout/somelayout.php''
<code php>
<code php>
<img src="<?php echo $OUTPUT->pix_url('imageone', 'theme');?>" alt="" />  
...
<img src="<?php echo $OUTPUT->pix_url('subdir/imagetwo', 'theme');?>" alt="" />
$templatecontext = [
...
$imageone => $OUTPUT->pix_url('imageone', 'theme'),
$imagetwo => $OUTPUT->pix_url('subdir/imagetwo', 'theme'),
...
];
 
echo $OUTPUT->render_from_template('theme_yourtheme/somelayout', $templatecontext);
</code>
</code>


We use a method of Moodle's output library to generate the URL to the image. Its not too important how that functions works but it is important that we use it as it is what allows images within Moodle to be over-rideable.
'''DO NOT''' include the image file extension. Moodle will work it out automatically and it will not work if you do include it.
'''DO NOT''' include the image file extension. Moodle will work it out automatically and it will not work if you do include it.


In this case rather than writing out the URL to the image we use a method of Moodle's output library. Its not too important how that functions works but it is important that we use it as it is what allows images within Moodle to be over-rideable.
''theme/yourtheme/templates/somelayout.mustache''
<code handlebars>
...
<img src="{{{imageone}}}" alt="Please give your image alt text or set the role to presentation" width="50" height="50">
<img src="{{{imagetwo}}}" alt="Please give your image alt text or set the role to presentation" width="50" height="50">
...
</code>
 
The following is how you would use the images from within CSS, SCSS or Less as background images.


The following is how you would use the images from within CSS as background images.
<code css>
<code css>
.divone {
.divone {
Line 609: Line 707:
}
}
</code>
</code>
If this case we have to use some special notations that Moodle looks for. Whenever Moodle hands out a CSS file it first searches for all ''[[something]]'' tags and replaces them with what is required.
If this case we have to use some special notations that Moodle looks for. Whenever Moodle hands out a CSS file it first searches for all ''[[something]]'' tags and replaces them with what is required.


Line 621: Line 720:
# /theme/themename/pix_core/moodlelogo.gif
# /theme/themename/pix_core/moodlelogo.gif
# /theme/themename/pix_core/i/user.gif
# /theme/themename/pix_core/i/user.gif
''Note that we have created a '''pix_core''' directory in our theme. For module images we need a '''pix_mod''' directory.
''Note that we have created a '''pix_core''' directory in our theme. For a specific activity module like chat we need a '''pix_plugins/mod/chat''' directory. This directory is "pix_plugins" and then the plugin type (mod) and then the plugin name (chat).  


Now the other very cool thing to mention is that Moodle looks for not just replacements of the same image type (jpg, gif, etc...) but also replacements in any image format. This is why above when working with our images we never specified the images file extension.
Now the other very cool thing to mention is that Moodle looks for not just replacements of the same image type (jpg, gif, etc...) but also replacements in any image format. This is why above when working with our images we never specified the images file extension.
Line 731: Line 830:


You can refer to the theme ''theme_more'' for an example on how to use this feature.
You can refer to the theme ''theme_more'' for an example on how to use this feature.
==Compiling SCSS on the fly==
{{Moodle 3.2}}
You can now provide a SCSS file that will be compiled (and cached) on the fly. The purpose of this feature is to dynamically allow the customisation of SCSS variables. See the [[SCSS|dedicated page on SCSS]].


==Unobvious Things==
==Unobvious Things==
Line 750: Line 854:
Some parts of Moodle may rely on particular divs, for example the div with id 'page-header'.
Some parts of Moodle may rely on particular divs, for example the div with id 'page-header'.


Consequently all themes must include at least the divs (with the same ids) that are present in the 'base' theme.  
Consequently all themes must include at least the divs (with the same ids) that are present in the 'boost' theme.  


Missing out these elements may result in unexpected behaviour within specific modules or other plugins.
Missing out these elements may result in unexpected behaviour within specific modules or other plugins.
==Caching==
When Moodle is not running in theme designer mode it will look for a cached version of the compiled CSS for the current theme to serve to the browser requesting the page. If the cached file doesn't yet exist then the CSS will be built and cached during the page request.
The cached CSS is located on disk in Moodle's local cache:
  <Moodle data directory >/localcache/theme/<global theme revision>/<theme_name>/css/all_<theme subrevision>.css
The cache path consists of a global theme revision (themerev config value) and a per theme subrevision (themesubrev plugin config value). If either of those are incremented it will change the path to the cache file and cause a new file to be generated.
Individual theme's CSS cache can be built by using the admin CLI script:
  php admin/cli/build_theme_css.php --themes boost
The script will only increment the theme subrevision of the theme(s) being built which means existing theme cache's remain untouched.


==Appendix A==
==Appendix A==
Line 763: Line 880:
|-
|-
|  $THEME->'''blockrtlmanipulations'''
|  $THEME->'''blockrtlmanipulations'''
|  Allows the theme to manipulate how the blocks are displayed in a ''right-to-left'' language.
|  Allows the theme to manipulate how the blocks are displayed in a ''right-to-left'' language. Not recommended since we automatically flip CSS for rtl.  
|-
|-
|  $THEME->'''csspostprocess'''
|  $THEME->'''csspostprocess'''
Line 839: Line 956:
|  $THEME->'''undeletableblocktypes'''
|  $THEME->'''undeletableblocktypes'''
|  An array of Block types that must exist on all pages in this theme or this theme will be unusable. If a block type listed here is missing when a page is loaded - it will be auto-created (but only shown for themes that require it).
|  An array of Block types that must exist on all pages in this theme or this theme will be unusable. If a block type listed here is missing when a page is loaded - it will be auto-created (but only shown for themes that require it).
|-
|  $THEME->'''addblockposition'''
|  Either BLOCK_ADDBLOCK_POSITION_FLATNAV, BLOCK_ADDBLOCK_POSITION_DEFAULT or BLOCK_ADDBLOCK_POSITION_CUSTOM. Defines where to put the "Add a block" controls when editing is enabled.
|}
|}


Line 903: Line 1,023:


==See also==
==See also==
* [[Theme changes in 2.0]]
* MoodleBites Theme Design - completely online courses [https://www.moodlebites.com/mod/page/view.php?id=3208 Level 1] and [https://www.moodlebites.com/mod/page/view.php?id=3210 Level 2] are designed to assist Moodle administrators, designers, and developers get up-to-speed with Moodle Theme design, and are run by [https://www.hrdnz.com HRDNZ] (Certified Moodle Partner since 2006).
* [[Adding courses and categories to the custom menu]]
* [[Making a horizontal dock]]
* [[Adding theme upgrade code]]
* [[Styling and customising the dock]]
* [[Changing topic or weekly outline to Page heading]]
* [[jQuery]]
 
===Moodle 2.2+===
 
* [[Themes 2.2 how to clone a Moodle 2.2 theme]]
 
===Moodle Bootstrap Themes - Moodle 2.5+===
 
* [[Themes 2.5 How to copy and customise the Simple (bootstrap) theme]]
 
[[de:Designs 2.0]]
 


[[Category:Plugins]]
[[Category:Plugins]]

Revision as of 08:48, 2 March 2019

Welcome to the new world of themes in Moodle!

A Moodle theme allows the user to change the look and feel of a Moodle site. Themes can be applied on the site, category, course and activity levels by users with permissions to do so. Themes can be designed for specific devices such as mobile phones or tablets. This page explains how themes work in Moodle and is intended to help you create or modify most themes for Moodle.

You can use contributed themes or create your entire own to share with the community. Themes can also be based on parent themes with only few customizations. Themes accomplish this using CSS, changing the underlying markup structure and also adding Javascript to add more advanced behaviors.

Most theme developers simply add a few changes to their new theme by basing it on an existing one. The Moodle Theme architecture is designed in such a way whereby the base theme will act as a fall-back that is used when nothing has been defined in the theme based on it. This makes it easy to create new themes that simply seek out to make minor changes.


Whats new?

When Moodle releases a new version. All changes that affect themes are listed in the theme/upgrade.txt file. This is the most up-to date place to find about all changes to a specific version of Moodle and will always be kept up to date.

View upgrade notes


The structure of a theme

Some important things to know when building good themes:

  1. config.php - this file is required in every theme. It defines configuration settings and definitions required to make the theme work in Moodle. These include theme, file, region, default region and options.
  2. Layouts and layout files - in config.php there is one definition for each page type (see Appendix A: Theme layouts for a list of over 12 types). Each page type definition tells Moodle which layout file will be used, what block regions this page type should display and so on. The layout file contains the HTML and the minimum PHP required to display basic structure of pages.
  3. The boost theme - is intended to be the best theme to use as a starting point for building a new theme. It supports all the latest theme features and it tries to stay as true to the Bootstrap css framework as possible. Creating a theme based on boost

Files and folders

A theme's files are placed in a folder with under moodle/theme folder and have subfolders. They are laid out like this:

Directory File Description
/ config.php Contains all of the configuration and definitions for each theme
/ lib.php Contains speciality functions that are used by theme
/classes *.php Contains auto-loaded classes for the theme. See Automatic class loading for more details.
/classes/output *.php This is the location for the theme to define overridden renderers. See Output renderers for more details.
/ settings.php Contains custom theme settings. These local settings are defined by the theme allowing an administrator to easily alter something about the way it looks or operates. (eg a background colour, or a header image)
/ version.php Contains the theme name, version number and Moodle version requirements for using the theme
/fonts/ *.woff, *.ttf, *.eot, *.svg, *.otf Theme fonts (since 2.6).
/fonts_core/ *.woff, *.ttf, *.eot, *.svg, *.otf Contains fonts that override standard core fonts (since 2.6).
/fonts_plugins/plugintype/pluginname/ *.woff, *.ttf, *.eot, *.svg, *.otf Contains fonts that override plugin fonts (since 2.6).
/amd/src/ *.js All specialty JavaScript files the theme requires should be located in here. Javascript should be written as an AMD module. For more information see Javascript Modules.
/lang/[langcode]/ *.php Any special language files the theme requires should be located in here.
/templates/ *.mustache Contains the mustache template files for the themes. (Including overridden ones). See Templates for more information.
/layout/ *.php Contains the layout files for the theme.
/pix/ *.png, *.jpg, *.gif, *.svg Contains any images the theme makes use of either in CSS or in the layout files.
/pix/ favicon.ico The favicon to display for this theme.
/pix/ screenshot.png A screenshot of the theme to be displayed in on the theme selection screen.
/pix_core/ *.png, *.jpg, *.gif, *.svg Contains images that override standard core images.
/pix_plugins/plugintype/pluginname/ *.png, *.jpg, *.gif, *.svg Contains images that override plugin images.
/style/ *.css Default location for CSS files.
/less/ *.less Default location for Less files if your theme uses less.
/scss/ *.scss Default location for SCSS files if your theme uses SCSS.

There are also several other places that stylesheets can be included from (see the CSS how and why section below).

It is possible to override icons used in base themes without interfering with core code by placing these in dataroot/pix and dataroot/pix_plugins. Where a theme extends a base theme and provides its own icons, these icons will still be used.

It is possible to override mustache templates used in base themes without interfering with core code by placing these in templates/[componentname]/[templatename].mustache.

Theme options

All theme options are set within the config.php file for the theme. The settings that are most used are: parents, sheets, layouts, and javascripts. Have a look at the theme options table for a complete list of theme options which include lesser used specialised or advanced settings.


Basic theme config example

Lets have a look at the boost theme configuration file and the different bits that make it up: defined('MOODLE_INTERNAL') || die();

require_once(__DIR__ . '/lib.php');

$THEME->name = 'boost'; $THEME->sheets = []; $THEME->editor_sheets = []; $THEME->scss = function($theme) {

   return theme_boost_get_main_scss_content($theme);

};

$THEME->layouts = [

   // Most backwards compatible layout without the blocks - this is the layout used by default.
   'base' => array(
       'file' => 'columns1.php',
       'regions' => array(),
   ),
   // Standard layout with blocks, this is recommended for most pages with general information.
   'standard' => array(
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre',
   ),
   // Main course page.
   'course' => array(
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre',
       'options' => array('langmenu' => true),
   ),
   'coursecategory' => array(
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre',
   ),
   // Part of course, typical for modules - default page layout if $cm specified in require_login().
   'incourse' => array(
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre',
   ),
   // The site home page.
   'frontpage' => array(
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre',
       'options' => array('nonavbar' => true),
   ),
   // Server administration scripts.
   'admin' => array(
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre',
   ),
   // My dashboard page.
   'mydashboard' => array(
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre',
       'options' => array('langmenu' => true),
   ),
   // My public page.
   'mypublic' => array(
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre',
   ),
   'login' => array(
       'file' => 'login.php',
       'regions' => array(),
       'options' => array('langmenu' => true),
   ),
   // Pages that appear in pop-up windows - no navigation, no blocks, no header.
   'popup' => array(
       'file' => 'columns1.php',
       'regions' => array(),
       'options' => array('nofooter' => true, 'nonavbar' => true),
   ),
   // No blocks and minimal footer - used for legacy frame layouts only!
   'frametop' => array(
       'file' => 'columns1.php',
       'regions' => array(),
       'options' => array('nofooter' => true, 'nocoursefooter' => true),
   ),
   // Embeded pages, like iframe/object embeded in moodleform - it needs as much space as possible.
   'embedded' => array(
       'file' => 'embedded.php',
       'regions' => array()
   ),
   // Used during upgrade and install, and for the 'This site is undergoing maintenance' message.
   // This must not have any blocks, links, or API calls that would lead to database or cache interaction.
   // Please be extremely careful if you are modifying this layout.
   'maintenance' => array(
       'file' => 'maintenance.php',
       'regions' => array(),
   ),
   // Should display the content and basic headers only.
   'print' => array(
       'file' => 'columns1.php',
       'regions' => array(),
       'options' => array('nofooter' => true, 'nonavbar' => false),
   ),
   // The pagelayout used when a redirection is occuring.
   'redirect' => array(
       'file' => 'embedded.php',
       'regions' => array(),
   ),
   // The pagelayout used for reports.
   'report' => array(
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre',
   ),
   // The pagelayout used for safebrowser and securewindow.
   'secure' => array(
       'file' => 'secure.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre'
   )

];

$THEME->parents = []; $THEME->enable_dock = false; $THEME->csstreepostprocessor = 'theme_boost_css_tree_post_processor'; $THEME->extrascsscallback = 'theme_boost_get_extra_scss'; $THEME->prescsscallback = 'theme_boost_get_pre_scss'; $THEME->yuicssmodules = array(); $THEME->rendererfactory = 'theme_overridden_renderer_factory'; $THEME->undeletableblocktypes = ;

Basic theme example settings explained

First up you will notice everything is added to $THEME. This is the theme's configuration object, it is created by Moodle using default settings and is then updated by whatever settings you add to it.

$THEME->name
The first setting, is the theme's name. This should simply be whatever your theme's name is, most likely whatever you named your theme directory.
$THEME->sheets
An array containing the names of the CSS stylesheets to include for this theme. Boost uses scss instead of css so it doesn't list any files here. Note that it is just the name of the stylesheet and does not contain the directory or the file extension. Moodle assumes that the theme's stylesheets will be located in the {theme}/style directory of the theme and have .css as an extension.
$THEME->editorsheets
An array containing the names of the CSS stylesheets to include for the TinyMCE text editor content area. Boost does not list any stylesheets here so TinyMCE will use plain text styles.
$THEME->layouts
In this example, boost maps each of the 18 different layout types to one of 6 different layout files. For more information see the layouts section below.
$THEME->parents
This defines the themes that the theme will extend. Boost has no parents, but if you were extending boost you would list it here like: $THEME->parents = ['boost'];
$THEME->enable_dock
Boost does not support docking blocks. For a example of a theme with a dock for blocks, see theme_bootstrapbase.
$THEME->csstreepostprocessor
Boost uses a function to post process the CSS. This is an advanced feature and is used in boost to automatically apply vendor prefixes to CSS styles.
$THEME->yuicssmodules
This is an old setting that defines a list of YUI css files to load. These files interfere with existing styles and it is recommended to set this to an empty string to prevent any files being included.
$THEME->rendererfactory
Almost all themes need this setting to be set to 'theme_overridden_renderer_factory' or the theme will not be able to customise any core renderers.
$THEME->undeletableblocktypes
This is a comma separated list of block types that cannot be deleted in this theme. If you don't define this - the admin and settings blocks will be undeletable by default. Because Boost provides alternate ways to navigate it does not require any blocks.

Note: When you first begin writing themes, make sure you take a look at the configuration files of the other themes that get shipped with Moodle. You will get a good picture of how everything works, and what is going on in a theme, simply by reading it and taking notice of what it is including or excluding.

CSS

Locations of CSS files

First lets look at where CSS can be included from within Moodle:

\theme\themename\style\*.css
This is the default location for all of the stylesheets that are used by a theme and the place which should be used by a theme designer if this theme is using CSS. Alternatives to CSS are LESS and SCSS which are more powerful, flexible and easier to maintain.

New theme developers should note that the order in which CSS files are found and included creates a hierarchy. This order ensures that the rules, within a theme's style sheets, take precedence over identical rules in other files that may have been introduced before. This can both extend another files definitions (see parent array in the config file) and also ensures that the current theme's CSS rules/definitions have the last say.

There are other locations that can be used (although very rarely) to include CSS in a page. A developer of a php file can manually specify a stylesheet from anywhere within Moodle, like the database. Usually, if code is doing this, it is because there is a non-theme config or plugin setting that contains information requires special CSS information. As a theme designer you should be aware of, but not have to worry about, these locations of CSS files. Here are some examples:

{pluginpath}\styles.css e.g. \block\blockname\styles.css or \mod\modname\styles.css
Every plugin can have its own styles.css file. This file should only contain the required CSS rules for the module and should not add anything to the look of the plugin such as colours, font sizes, or margins other than those that are truly required.
Theme specific styles for a plugin should be located within the themes styles directory.
{pluginpath}\styles_themename.css
This should only ever be used by plugin developers. It allows them to write CSS that is designed for a specific theme without having to make changes to that theme. You will notice that this is never used within Moodle and is designed to be used only by contributed code.

As theme designers, we will only use the first method of introducing CSS: adding rules to a stylesheet file located in the theme's style directory.

Better than CSS

Browsers understand CSS well - but it is hard to write and maintain. The language does not support inheritance and reuse. Support for variables exists in more modern browsers only. This is why CSS pre-processors were invented. Moodle supports 2 different types of CSS pre-processors (Less and Sass) but the Sass pre-processor is recommended by far.

For more information on Less and Sass see:

To use either one, define $THEME->lessfile = 'filename'; or $THEME->scss = 'filename'; in your themes config.php. Moodle will then use one of it's in-built CSS pre-processor to compile the CSS the first time it is loaded (or everytime if themedesignermode is enabled in $CFG).

Moodle's core CSS organisation

The next thing to look at is the organisation of CSS and rules within a theme. Although as a theme designer it is entirely up to you as to how you create and organise your CSS. Please note that none of the themes provided in the standard install by Moodle use CSS stylesheets directly. Instead they use either LESS or SCSS to generate the style sheets. They all list one main LESS or SCSS file named "moodle" in the less or scss folders and this file uses imports to load all other required files.

How to write effective CSS rules within Moodle

Writing good CSS rules is incredibly important.

Due to performance requirements and browser limitations, all of the CSS files are combined into a single CSS file that gets included every time. This means that rules need to be written in such a way as to minimise the chances of a collision leading to unwanted styles being applied. Whilst writing good CSS is something most designers strive for we have implemented several new body classes and prompt developers to use appropriate classnames.

<body> CSS id and classes

The ID tag that gets applied to the body will always be a representation of the URI. For example if you are looking at a forum posting and the URI is '/mod/forum/view.php' then the body tags ID will be '#page-mod-forum-view'.

As well as the body's ID attribute the URI is also exploded to form several CSS classes that get added to the body tag, so in the above example '/mod/forum/view' you would end up with the following classes being added to the body tag '.path-mod', '.path-mod-forum'. Note that '.path-mod-forum-view' is not added as a class, this is intentionally left out to lessen confusion and duplication as rules can relate directly to the page by using the ID and do not require the final class.

The body ID and body classes described above will form the bread and butter for many of the CSS rules you will need to write for your theme, however there are also several other very handy classes that get added to the body tag that will be beneficial to you once you start your journey down the rabbit hole that is themeing. Some of the more interesting classes are listed below.

  • If JavaScript is enabled then 'jsenabled' will be added as a class to the body tag allowing you to style based on JavaScript being enabled or not.
  • Either 'dir-rtl' or 'dir-ltr' will be added to the body as a class depending on the direction of the language pack: rtl = right to left, ltr = left to right. This allows you to determine your text-alignment based on language if required.
  • A class will be added to represent the language pack currently in use, by default en_utf8 is used by Moodle and will result in the class 'lang-en_utf8' being added to the body tag.
  • The wwwroot for Moodle will also be converted to a class and added to the body tag allowing you to stylise your theme based on the URL through which it was reached. e.g. http://sam.moodle.local/moodle/ will become '.sam-moodle-local—moodle'
  • If the current user is not logged then '.notloggedin' will be added to the body tag.
  • The course format type will be added such as format-weeks
  • The course id, context id and category id are all added as in "course-11 context-616 cmid-202 category-1"
  • The pagelayout is added as "pagelayout-incourse"

What does all of this look like in practise? Well using the above example /mod/forum/view.php you would get at least the following body tag: <body id="page-mod-forum-view" class="format-weeks forumtype-social path-mod path-mod-forum safari dir-ltr lang-en yui-skin-sam yui3-skin-sam damyon-per-in-moodle-com--stable_master pagelayout-incourse course-11 context-616 cmid-202 category-1 jsenabled">

Writing your rules

By following the CSS coding style and CSS best-practices and understanding the cascading order of CSS a theme developer will reduce collisions and lines of CSS that is written. CSS classes have been placed where it is believed anyone may want to apply their own styles.

When starting to write rules make sure that you have a good understanding of where you want those rules to be applied, it is a good idea to make the most of the body classes mentioned above. If you want to write a rule for a specific page make use of the body tag's ID, e.g.:

#page-mod-forum-view .forumpost {

   border: 1px solid blue;

}

If you want to write a rule that will be applied all throughout the forum.:

.path-mod-forum .forumpost {

   border: 1px solid blue;

}

The other very important thing to take into consideration is the structure leading up to the tag you want to style. Browsers apply conflicting styles with priority on the more specific selectors. It can be very beneficial to keep this in mind and write full selectors that rely on the structure of the tags leading to the tag you wish to style.

By making use of body id's and classes and writing selectors to take into account the leading structure you can greatly minimise the chance of a collision both with Moodle now and in the future.

It is also important to write as FEW rules as possible. CSS is extremely hard to maintain and lots of CSS is bad for client side performance. Themes based on the Bootstrap CSS framework can achieve most things without writing a single additional CSS rule. Please read the Bootstrap documentation and learn how to use Bootstrap well to avoid adding unnecessary CSS rules for things already provided by the framework.

Layouts

Layouts are defined in config.php.

All themes are required to define the layouts they wish to be responsible for as well as create; however, many layout files are required by those layouts. If the theme is overriding another theme then it is a case of deciding which layouts this new theme should override. If the theme is a completely fresh start then you will need to define a layout for each of the different possibilities.

It is also important to note that a new theme that will base itself on another theme (overriding it) does not need to define any layouts or use any layout files if there are no changes that it wishes to make to the layouts of the existing theme.

As mentioned earlier, layouts are defined in config.php within $THEME->layouts. The following is an example of one such layout definition: $THEME->layouts = array(

   // Standard layout with blocks, this is recommended for most pages with general information
   'standard' => array(
       'theme' => 'boost',
       'file' => 'columns2.php',
       'regions' => array('side-pre'),
       'defaultregion' => 'side-pre'
   )

) The first thing Moodle looks at is the name of the layout, in this case it is `standard` (the array key in PHP), it then looks at the settings for the layout, this is the theme, file, regions, and default region. There are also a couple of other options that can be set by a layout.

theme
is the theme the layout file exists in. That's right: you can make use of layouts from other installed themes. Optional
file
is the name of the layout file this layout wants to use. Required
regions
is the different block regions (places you can put blocks) within the theme. Required
defaultregion
is the default location when adding new blocks. Required if regions is non-empty, otherwise optional
options
an array of layout specific options described in detail below. Optional

The theme is optional. Normally the the layout file is looked for in the current theme, or, if it is not there, in the parent theme. However, you can use a layout file from any other theme by giving the theme name here.

You can define whatever regions you like. You just need to pick a name for each one. Most themes just use one or both of side_pre and side_post, which is like 'left side' and 'right side', except in right to left languages, when they are reversed. If you say in config.php that your the layout provides regions called 'fred' and 'barney', then you must call $OUTPUT->blocks_for_region('fred') and $OUTPUT->blocks_for_region('barney') somewhere in the layout file.

The final setting options is a special case that only needs to be set if you want to make use of it. This setting allows the theme designer to specify special options that they would like to create that can be later accessed within the layout file. This allows the theme to make design decisions during the definition and react upon those decisions in what ever layout file is being used.

One such place this has been used is within the boost theme. If you take a look first at theme/boost/config.php you will notice that several layouts specify options langmenu and nonavbar which can both be set to either true or false. The layout options can then be used on the layout .php files, mustache templates and renderers. <?php $hasnavbar = (empty($PAGE->layout_options['nonavbar']) && $PAGE->has_navbar()); $hasfooter = (empty($PAGE->layout_options['nofooter'])); ?>

Layout files

Layout files are used to provide a different layout of the elements of the page for different types of pages in Moodle.

In the config.php for a theme - there is a list of 'layouts' which map a page type to a specific php page in the layout folder for the theme.

For example: theme/boost/config.php ...

   'popup' => array(                                                                                                               
       'file' => 'columns1.php',                                                                                                   
       'regions' => array(),                                                                                                       
       'options' => array('nofooter' => true, 'nonavbar' => true),                                                                 
   ),                                

...

This means every page that has pagetype 'popup' will be displayed with the 'theme/themename/layout/columns1.php' file, it will have no block regions and there are some options that will be available to the page in the global variable "$PAGE->layout_options".

It is possible to implement a layout file directly in php by echoing the HTML for the page, or mixing php tags with HTML - but a better way to create a layout file is to gather all the information required for the layout into a context and render it with a mustache template.

Read about mustache templates

Using templates for layout files makes a lot of sense because they are easier to read and maintain than mixing PHP and HTML in the same file.

A simple example of a layout file using a template is at:

theme/boost/layout/columns1.php <?php

$bodyattributes = $OUTPUT->body_attributes([]);

$templatecontext = [

   'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),        
   'output' => $OUTPUT,                                                                                                            
   'bodyattributes' => $bodyattributes                                                                                             

];

echo $OUTPUT->render_from_template('theme_boost/columns1', $templatecontext);

This example puts some variables into a templatecontext and then calls "render_from_template" to render the mustache template for this layout. The template is located at: "theme/boost/templates/columns1.mustache". It is possible to put PHP classes in the context for the mustache template - and any public properties or methods which accept no arguments will be available to the template. $OUTPUT has several useful public methods which accept no arguments and is a valuable class when creating a layout template in mustache.

The mustache template for this layout is shown here:

theme/boost/templates/columns1.mustache {{{ output.doctype }}} <html {{{ output.htmlattributes }}}> <head>

   <title>{{{ output.page_title }}}</title>                                                                                        
   <link rel="shortcut icon" href="{{{ output.favicon }}}" />                                                                      
   {{{ output.standard_head_html }}}                                                                                               
   <meta name="viewport" content="width=device-width, initial-scale=1.0">                                                          

</head>

<body {{{ bodyattributes }}}>

   {{{ output.standard_top_of_body_html }}}                                                                                        
                                                                                                                                   
               <section id="region-main">                                                                                          
                   {{{ output.course_content_header }}}                                                                            
                   {{{ output.main_content }}}                                                                                     
                   {{{ output.course_content_footer }}}                                                                            
               </section>                                                                                                          

{{{ output.standard_end_of_body_html }}} </body> </html> {{#js}} require(['theme_boost/loader']); Template:/js

Explaining each line of this template will answer a lot of questions. This example contains only the very minimal required functions to generate a valid layout. You should consider all of the sections below as required in every layout file (although any of the HTML tags can and should be altered).

{{{ output.doctype }}}

This is an example of calling a function on $OUTPUT in php. Because there is a public method on the output class named "doctype" which accepts no arguments - mustache will call it and return the output. We call a function to generate the doctype tag because calling this function returns us the correct HTML for the document type for this theme AND it sets a different content type header (including the charset) depending on the doc type for the theme. Setting a correct charset in every page is important to prevent a class of XSS attacks.

<html {{{ output.htmlattributes }}}>

Now we have returned our root tag - the html tag. We included a set of default attributes for the page by calling the htmlattributes function of our output class. This includes the correct language attribute for the entire page and can include an xml namespace for XHTML documents.

<head>

   <title>{{{ output.page_title }}}</title>                                                                                        

Now we have started the head section of our document and set the title for the page. Notice the title is already escaped by the output class so we are using triple mustache tags "{{{" to avoid double escaping.

   <link rel="shortcut icon" href="{{{ output.favicon }}}" />                                                                      

We call a function to get the url to the favicon. The favicon is a file in the theme pix directory and it is served through the "theme/image.php" file which adds special caching headers for images.

   {{{ output.standard_head_html }}}                                                                                               

The standard head html function performs a lot of setup that is required for our page. It internally creates the block regions, creates meta tags including keywords for SEO, initialises the common javascript modules, generates links to the style sheets and injects any additional HTML set by the $CFG->additionalhtmlhead setting.

   <meta name="viewport" content="width=device-width, initial-scale=1.0">                                                          

</head>

This viewport meta tag is recommended by bootstrap for "proper viewport rendering and touch zooming".

<body {{{ bodyattributes }}}>

The body attributes include the language direction and standard classes for the page.

In the Boost theme we use a page-wrapper div to prevent content from disappearing under the fixed header.

   {{{ output.standard_top_of_body_html }}}                                                                                        
                                                                                                                                   

The standard_top_of_body_html should be included in every layout and includes skip links for accessibility as well as initialising jquery, yui and our own static javascript files.

               <section id="region-main">                                                                                          

This is standard HTML tags defining the content region for this page. The classes come from bootstrap 4.

                   {{{ output.course_content_header }}}                                                                            

The course content header allows Moodle plugins to inject things in the top of the page. This is used for "notifications" for example (which are the alert boxes you see after submitting a form).

                   {{{ output.main_content }}}                                                                                     

The main content function returns the real content for the page.

                   {{{ output.course_content_footer }}}                                                                            

The course content footer is used mainly by course formats to insert things after the main content.

               </section>                                                                                                          

We close all our open tags...

{{{ output.standard_end_of_body_html }}}

This function will add all of the javascript that was required while rendering the page. Javascript is added at the end of the document so that it does not block rendering the page. </body> </html>

We finish the HTML for our page. {{#js}} require(['theme_boost/loader']); Template:/js

This final section is required for bootstrap 4 themes and loads all the Bootstrap 4 Javascript dependencies.

If we had block regions in this layout we would need to insert them in the template. The way we would do this is by getting the HTML for the block region in our layout php file, adding it to the context and then including it in our template.

theme/boost/layout/columns2.php ... $blockshtml = $OUTPUT->blocks('side-pre'); $hasblocks = strpos($blockshtml, 'data-block=') !== false; ... $templatecontext = [ ...

   'sidepreblocks' => $blockshtml,                                                                                                 
   'hasblocks' => $hasblocks,

... ]; ... echo $OUTPUT->render_from_template('theme_boost/columns2', $templatecontext);

theme/boost/templates/columns2.mustache ...

               {{#hasblocks}}                                                                                                      
               <section data-region="blocks-column" class="hidden-print">                                                          
                   {{{ sidepreblocks }}}                                                                                           
               </section>                                                                                                          
               Template:/hasblocks 

...

When writing layout files think about the different layouts and how the HTML that each makes use of will differ. You will most likely find you do not need a different layout file for each layout, most likely you will be able to reuse the layout files you create across several layouts. You can of course make use of layout options as well to further reduce the number of layout files you need to produce.

Of course as mentioned above if you are customising an existing theme then you may not need to create any layouts or layout files at all.

Language File

You need to create a language file for your theme with a few standard strings in it. At a minimum create a file called lang/en/theme_themename.php in your theme folder. For example, the 'boost' theme has a language file called lang/en/theme_boost.php.

You must define the following lines in your file (example is from Boost theme, adapt as required):

$string['pluginname'] = 'Boost'; $string['region-side-pre'] = 'Right'; $string['choosereadme'] = 'Boost is a modern highly-customisable theme. This theme is intended to be used directly, or as a parent theme when creating new themes utilising Bootstrap 4.';

Without the above you will get notices for the missing strings.

Making use of images

Right at the start when listing the features of the new themes system one of the features mentioned was the ability to override any of the standard images within Moodle from within your theme. At this point we will look at both how to make use of your own images within your theme, and secondly how to override the images being used by Moodle. So first up a bit about images within Moodle,

  1. Images you want to use within your theme need to be located within your theme's pix directory.
  2. You can use sub directories within the pix directory of your theme.
  3. Images used by Moodle's core are located within the pix directory of Moodle.
  4. Modules, blocks and other plugins should also store their images within a pix directory.

So making use of your own images first up. Lets assume you have added two image files to the pix directory of your theme.

  • /theme/yourthemename/pix/imageone.jpg
  • /theme/yourthemename/pix/subdir/imagetwo.png

Notice that one image is a JPEG image, and the second is a PNG. Also the second image is in a subdirectory.

The following code snippet illustrates how to make use of your images within your layout file so they can be inserted by your layout template. theme/yourtheme/layout/somelayout.php ... $templatecontext = [ ... $imageone => $OUTPUT->pix_url('imageone', 'theme'), $imagetwo => $OUTPUT->pix_url('subdir/imagetwo', 'theme'), ... ];

echo $OUTPUT->render_from_template('theme_yourtheme/somelayout', $templatecontext);

We use a method of Moodle's output library to generate the URL to the image. Its not too important how that functions works but it is important that we use it as it is what allows images within Moodle to be over-rideable. DO NOT include the image file extension. Moodle will work it out automatically and it will not work if you do include it.

theme/yourtheme/templates/somelayout.mustache ... <img src="{{{imageone}}}" alt="Please give your image alt text or set the role to presentation" width="50" height="50"> <img src="{{{imagetwo}}}" alt="Please give your image alt text or set the role to presentation" width="50" height="50"> ...

The following is how you would use the images from within CSS, SCSS or Less as background images.

.divone {

   background-image: url(imageone);

}

.divtwo {

   background-image: url(subdir/imagetwo);

}

If this case we have to use some special notations that Moodle looks for. Whenever Moodle hands out a CSS file it first searches for all something tags and replaces them with what is required.

The final thing to notice with both of the cases above is that at no point do we include the images file extension. The reason for this leads us into the next topic, how to override images.

From within a theme you can VERY easily override any standard image within Moodle by simply adding the replacement image to the theme's pix directory in the same sub directory structure as it is in Moodle. So for instance we wanted to override the following two images:

  1. /pix/moodlelogo.gif
  2. /pix/i/user.gif

We would simply need to add our replacement images to the theme in the following locations

  1. /theme/themename/pix_core/moodlelogo.gif
  2. /theme/themename/pix_core/i/user.gif

Note that we have created a pix_core directory in our theme. For a specific activity module like chat we need a pix_plugins/mod/chat directory. This directory is "pix_plugins" and then the plugin type (mod) and then the plugin name (chat).

Now the other very cool thing to mention is that Moodle looks for not just replacements of the same image type (jpg, gif, etc...) but also replacements in any image format. This is why above when working with our images we never specified the images file extension. This means that the following would also work:

  1. /theme/themename/pix_core/moodlelogo.png
  2. /theme/themename/pix_core/i/user.bmp

For a more detailed description of how this all works see the page on Using images in a theme.

Adding custom fonts

Moodle 2.6


CSS3 standard introduced the possibility to specify custom fonts, see CSS3 Fonts tutorial.

Since 2.6 Moodle includes support for plugin or theme fonts. It is very similar to theme images and pix subdirectories.

Font file locations

Depending on where you intend to use the font put it into one of the following locations:

  • /lib/fonts/ - fonts used in core
  • /plugindir/fonts/ - fonts used by plugin
  • /theme/sometheme/fonts/ - theme specific fonts

You can also override core and plugin fonts in theme:

  • /theme/sometheme/fonts_core/ - overridden core fonts
  • /theme/sometheme/fonts_plugins/plugintype_pluginname/ - overridden fonts of some plugin

Notes:

  • subdirectories are not allowed
  • use only lowercase alphanumeric characters and underscore in font file names
  • WOFF (Web Open Font Format), TTF (True Type Fonts), OTF (OpenType Fonts), SVG (Scalable Vector Graphic) and EOT (Embedded OpenType) fonts are supported, but for the sake of humanity (And MDL_15169 ) please use only WOFF fonts to encourage the quick death of IE8.

CSS placeholders

@font-face {

   font-family: ThreeDumb;
   src: url(3dumb.woff);

}

The placeholder references file /mod/book/fonts/3dumb.woff, the new fontface could be for example used for book headings:

.path-mod-book .book_chapter_title {

   font-family: ThreeDumb;

}

If you want to use some font in theme only, you can for example:

@font-face {

   font-family: goodDogFont;
   src: url(good_dog.woff);

}

a {font-family:goodDogFont;}

The font would be stored in /theme/yourtheme/fonts/good_dog.woff file.

More free fonts

Please respect all licenses for font redistribution, you can get some nice free fonts from http://www.fontsquirrel.com for example.

Warning

This is not intended for forcing of something like Comic Sans on all your visitors...

Compiling LESS on the fly

Moodle 2.7


You can now provide a LESS file that will be compiled (and cached) on the fly. The purpose of this feature is to dynamically allow the customisation of LESS variables.

Set up your theme

  1. Create a .less file in a less folder. Eg. theme/yourtheme/less/myfile.less
  2. Edit your theme config file, and set $THEME->lessfile to the name of your file (do not include .less). Eg. $THEME->lessfile = 'myfile'

That's it, the LESS file will be compiled and included in the page on the fly, but that is not very useful yet.

Please note that any file referenced in $THEME->sheets that shares the same name than the LESS file will be silently ignored.

Inheriting from a parent

Even if your theme is inheriting from a parent, the LESS file itself will not inherit from anything, this is something you should do manually. For instance, if you want your LESS file to include all of the LESS code provided by theme_bootstrapbase, usually to change the variables, you need to manually import the file like this:

@import "../../bootstrapbase/less/moodle.less";

The path needs to be relative and not absolute. You would definitely want to add that rule first in your file and add anything else below it.

Programmatically injecting LESS

There are two theme options to specify a callback function that you need to know about:

  1. $THEME->extralesscallback: To return raw LESS code to be injected.
  2. $THEME->lessvariablescallback: To return an array of variables and their values.

Typically you will want to simply inject variables, but if you need to perform more complex manipulations, you can return some raw LESS code. The variables returned by the callback are always injected last.

Performance

Compiling LESS on the fly is a slow operation, and even though the result is cached you should be aware of it. If you have enabled the configuration setting themedesignermode you will definintely notice the slowness as the cache only lives for a very short period of time. Ideally your theme should precompile the LESS into CSS, but if you want to provide theme settings to your user, then using this feature is for you.

Example

You can refer to the theme theme_more for an example on how to use this feature.

Compiling SCSS on the fly

Moodle 3.2


You can now provide a SCSS file that will be compiled (and cached) on the fly. The purpose of this feature is to dynamically allow the customisation of SCSS variables. See the dedicated page on SCSS.

Unobvious Things

Getting Your Theme to Appear Correctly in Theme Selector

If you follow the examples on this page to the letter, when you go to the Theme Selector page you may be discouraged to find that your theme does not appear like the other themes do. In fact, instead of your theme's name, you will see something along the lines of [[pluginname]].

To correct this, you must add the theme/THEMENAME/lang/en/theme_THEMENAME.php file, where THEMENAME is the name of the theme folder. Inside that file, add the string "$string['pluginname'] = 'THEMENAME'; ". Make THEMENAME the name of your theme, however you want it displayed in the Theme selector.

Also, make sure to change your config.php file and version.php file to reflect the correct name:

In config.php: $THEME->name = 'NAME';

In version.php: $plugin->component = 'theme_NAME'; // Full name of the plugin (used for diagnostics)

The screenshot for the theme should be about 500x400 px.

Required theme divs

Some parts of Moodle may rely on particular divs, for example the div with id 'page-header'.

Consequently all themes must include at least the divs (with the same ids) that are present in the 'boost' theme.

Missing out these elements may result in unexpected behaviour within specific modules or other plugins.

Caching

When Moodle is not running in theme designer mode it will look for a cached version of the compiled CSS for the current theme to serve to the browser requesting the page. If the cached file doesn't yet exist then the CSS will be built and cached during the page request.

The cached CSS is located on disk in Moodle's local cache:

 <Moodle data directory >/localcache/theme/<global theme revision>/<theme_name>/css/all_<theme subrevision>.css

The cache path consists of a global theme revision (themerev config value) and a per theme subrevision (themesubrev plugin config value). If either of those are incremented it will change the path to the cache file and cause a new file to be generated.

Individual theme's CSS cache can be built by using the admin CLI script:

 php admin/cli/build_theme_css.php --themes boost

The script will only increment the theme subrevision of the theme(s) being built which means existing theme cache's remain untouched.

Appendix A

Theme options

Setting Effect
$THEME->blockrtlmanipulations Allows the theme to manipulate how the blocks are displayed in a right-to-left language. Not recommended since we automatically flip CSS for rtl.
$THEME->csspostprocess Allows the user to provide the name of a function that all CSS should be passed to before being delivered.
$THEME->csstreepostprocessor (Since 3.2) Allows the user to provide the name of a function that can perform manipulations on an in-memory representation of the CSS tree. Some useful manipulations are available such as the "theme_boost\autoprefixer" which will automatically add vendor prefixes to all CSS that requires them.
$THEME->doctype The doctype of the served documents.
$THEME->editor_sheets An array of stylesheets to include just within the body of the TinyMCE text editor. This is required if you want content to resemble its final appearance in the page, while it is being edited in the text editor.
$THEME->enablecourseajax If set to false the course AJAX features will be disabled.
$THEME->enable_dock If set to true the side dock is enabled for blocks.
$THEME->prescsscallback The name of a function that will return some SCSS code to inject at the beginning of the SCSS file specified in $THEME->scss. (Since 3.2)
$THEME->extrascsscallback The name of a function that will return some SCSS code to inject at the end of the SCSS file specified in $THEME->scss. (Since 3.2)
$THEME->extralesscallback The name of a function that will return some LESS code to inject at the end of the LESS file specified in $THEME->lessfile. (Since 2.7)
$THEME->hidefromselector Used to hide a theme from the theme selector (unless theme designer mode is on). Accepts true or false.
$THEME->javascripts An array containing the names of JavaScript files located in /javascript/ to include in the theme. (gets included in the head). This setting should no longer be used - please use AMD Javascript Modules instead.
$THEME->javascripts_footer As above but will be included in the page footer. This setting should no longer be used - please use AMD Javascript Modules instead.
$THEME->layouts An array setting the layouts for the theme
$THEME->lessfile The name of a LESS file in the theme's less/ folder to compile on the fly. Sheets with the same name will be ignored. (Since 2.7)
$THEME->lessvariablescallback The name of a function that will modify some LESS variables before compiling the LESS file specified in $THEME->lessfile. (Since 2.7)
$THEME->scss The name of a SCSS file in the theme's scss/ folder to compile on the fly. Sheets with the same name will be ignored. This can also be a function which returns SCSS, in which case all import paths will be relative to the scss folder in this theme or any of it's parents. (Since 3.2)
$THEME->name Name of the theme. Most likely the name of the directory in which this file resides.
$THEME->parents An array of themes to inherit from. If the theme you inherit from inherits from a parent as well, you need to indicate the grand parent here too.
$THEME->parents_exclude_javascripts An array of JavaScript files NOT to inherit from the themes parents
$THEME->parents_exclude_sheets An array of stylesheets not to inherit from the themes parents
$THEME->plugins_exclude_sheets An array of plugin sheets to ignore and not include.
$THEME->renderfactory Sets a custom render factory to use with the theme, used when working with custom renderers. You most likely want this set to "theme_overridden_renderer_factory".
$THEME->sheets An array of stylesheets to include for this theme. Should be located in the theme's style directory. Not required if using less or scss.
$THEME->yuicssmodules An array of YUI CSS modules to be included. This setting should probably be set to to prevent and YUI CSS being included.
$THEME->undeletableblocktypes An array of Block types that must exist on all pages in this theme or this theme will be unusable. If a block type listed here is missing when a page is loaded - it will be auto-created (but only shown for themes that require it).
$THEME->addblockposition Either BLOCK_ADDBLOCK_POSITION_FLATNAV, BLOCK_ADDBLOCK_POSITION_DEFAULT or BLOCK_ADDBLOCK_POSITION_CUSTOM. Defines where to put the "Add a block" controls when editing is enabled.

The different layouts as of 21st April 2013

Layout Purpose
base Most backwards compatible layout without the blocks - this is the layout used by default.
standard Standard layout with blocks, this is recommended for most pages with general information.
course Main course page.
coursecategory Use for browsing through course categories.
incourse Default layout while browsing a course, typical for modules.
frontpage The site home page.
admin Administration pages and scripts.
mydashboard My dashboard page.
mypublic My public page.
login The login page.
popup Pages that appear in pop-up windows - no navigation, no blocks, no header.
frametop Used for legacy frame layouts only. No blocks and minimal footer.
embedded Used for embedded pages, like iframe/object embedded in moodleform - it needs as much space as possible
maintenance Used during upgrade and install. This must not have any blocks, and it is a good idea if it does not have links to other places - for example there should not be a home link in the footer.
print Used when the page is being displayed specifically for printing.
redirect Used when a redirection is occurring
report Used for reports.
secure Used for safebrowser and securewindow.

See also

  • MoodleBites Theme Design - completely online courses Level 1 and Level 2 are designed to assist Moodle administrators, designers, and developers get up-to-speed with Moodle Theme design, and are run by HRDNZ (Certified Moodle Partner since 2006).