Overriding a renderer
This document explains renderers and how to override a renderer within a theme in order to achieve a unique look. It assumes you have a grounding in PHP and have read Themes.
An overview of renderers
A renderer is a class that handles all of the output for a component of Moodle. It should contain no logic other than what is required to generate the display, and should be compartmentalised into functional chunks, each of which should be responsible for producing a widget or control used within the component. This output will vary depending on what the component is, for example the forum will have a method for displaying a forum post, displaying a thread (which most likely calls the forum post method), and displaying a search form.
These renderers are used as much as possible in Moodle and code is constantly being converted. As Moodle progresses, more of the code should be utilising renderers.
As well as the component renderers there is a core renderer, which is responsible for producing the display for many general things within Moodle, for example tables, action icons, and block regions.
Why use these renderers? They help developers separate the logic and the display when writing code and for theme designers they allow a means by which to take total control of the HTML that Moodle produces.
How do you do that you ask? By overriding them!
Getting started
We will create a test theme that extends the standard theme and does nothing other than override renderers.
1. Open your Moodle installation's theme directory and within it create a new directory called overridetest. Within the newly created overridetest directory create a config.php file and add the following PHP to it:
<?php
$THEME->name = 'overridetest';
$THEME->parents = array('standard', 'base');
This tells Moodle that our theme is called overridetest and that it extends both the base and standard themes.
2. The next step is to tell the theme that we want to override renderers. This is simple - we just tell Moodle which render factory we want our theme to use.
A render factory is a class that Moodle will use to find renderers. It is required by Moodle because renderers can exist in several locations within Moodle. These factories are what allow us to override only the renderers we want to rather than having to override them all.
3. To the bottom of the config.php add the following line:
$THEME->rendererfactory = 'theme_overridden_renderer_factory';
This line tells Moodle that for this theme we want to use the theme_overridden_renderer_factory, a special class tells Moodle to look for renderers first within the theme and then in all of the other default locations.
Templates
As of Moodle 2.9 it is preferable to use Templates rather than generating HTML from php with html_writer.
All about html_writer (Moodle 2.8 and older)
The html_writer class is used quite a bit and so it's important to understand what it is actually doing.
While you could write all of your HTML manually within a renderer method, it is encouraged to use the html_writer where possible to keep all renderer code similar and easily readable.
The following examples will introduce you to the html_writer, what some of it's core methods are, and what they produce.
html_writer::start_tag
This is used to produce an opening tag and takes two arguments - the first the tag to open and the second is the attributes to give the tag. The attributes are an array of key value pairs where the key is the attribute and the value is the value. Things such as class, id, and type are attributes that can be specified in this way.
<?php echo html_writer::start_tag('div', array('class'=>'blah')); ?>
<div class="blah">
<?php echo html_writer::start_tag('table', array('class'=>'generaltable', 'id'=>'mytable')); ?>
<table class="generaltable" id="mytable">
html_writer::end_tag
This method is used to simply produce a closing tag for a tag that has been opened by html_writer::start_tag.
It takes just one argument the tag to close.
<?php echo html_writer::end_tag('div'); ?>
</div>
<?php echo html_writer::end_tag('table'); ?>
</table>
html_writer::tag
This method takes three arguments and is the combination of start_tag and end_tag plus the inclusion of content.
<?php echo html_writer::tag('div', 'I am some content to go into the string', array('class'=>'blah')); ?>
<div class="blah">I am some content to go into the string</div>
html_writer::empty_tag The final method empty_tag is used to create a self closing tag. This is useful in situations such as producing image tags, or input fields. It takes two arguments - tag and attributes, the same as start_tag.
<?php echo html_writer::empty_tag('img', array('src'=>'myimage.png', 'alt'=>'This is my image')); ?>
<img src="myimage.png" alt="This is my image" />
<?php echo html_writer::empty_tag('input', array('type'=>'text', 'name'=>'afield', 'value'=>'This is a field')); ?>
<input type="input" name="afield" value="This is a field" />
Our first renderer
A note about autoloading and namespaces. Moodle 2.8 introduced support for autoloading for renderers. The examples in this page will work on older versions of Moodle, but the autoloaded alternatives are listed in notes for each section.
1. Create a file renderers.php within the root of the theme's directory. (This could also be renamed "classes/core_renderer.php to use auto-loading).
The first renderer that we will override is the core renderer for Moodle.
2. Within your renderers.php add the following:
<?php
class theme_overridetest_core_renderer extends core_renderer {
}
What we have added is the class definition for our first renderer. Note:
- The name of the renderer is VERY important it is made up of three sections joined by underscores:
- theme : This will always be the same when overriding renderers from the theme.
- overridetest : This is of course the name of our theme, and should be the name of the theme the renderer is being overridden from.
- core_renderer : This is the renderer that we are overriding.
- The renderer should always extend the renderer it is overriding. This ensures we don't need to override every method the renderer offers.
With our renderer class defined it's now time to override our first display method.
In this case you we will override the heading() method that the core renderer has. This method is used to display a simple header in the page consisting of an h* tag (h2, h3, etc) and the heading itself.
The existing code for the function is as follows:
/**
* Outputs a heading
* @param string $text The text of the heading
* @param int $level The level of importance of the heading. Defaulting to 2
* @param string $classes A space-separated list of CSS classes
* @param string $id An optional ID
* @return string the HTML to output.
*/
public function heading($text, $level = 2, $classes = 'main', $id = null) {
$level = (integer) $level;
if ($level < 1 or $level > 6) {
throw new coding_exception('Heading level must be an integer between 1 and 6.');
}
return html_writer::tag('h' . $level, $text, array('id' => $id, 'class' => renderer_base::prepare_classes($classes)));
}
Pretty simple really, it takes four standard arguments, and outputs a h* tag.
There are several important things to take away from this very simple bit of code:
- The first three lines are to check that $level is an integer between 1 and 6, this is because that integer gets written to the tag.
- The final line creates a h* tag with the arguments and returns it as a string.
- The output is returned rather than being echoed to the page. ALL renderer methods must do this.
- The html_writer class is used to generate the output rather than creating the string within the function. This is highly recommended for all renderer methods.
3. Let's override this method within our themes renderer. Within our method we will aim to wrap the h* tag in a div with a class name we choose and then add an image right before the h* tag is printed.
The following code does exactly this:
public function heading($text, $level = 2, $classes = 'main', $id = null) {
$content = html_writer::start_tag('div', array('class'=>'headingcontainer'));
$content .= html_writer::empty_tag('img', array('src'=>$this->pix_url('headingpic', 'theme'), 'alt'=>'', 'class'=>'headingimage'));
$content .= parent::heading($text, $level, $classes, $id);
$content .= html_writer::end_tag('div');
return $content;
}
Looking at the code line by line:
$content = html_writer::start_tag('div', array('class'=>'headingcontainer'));
Here we are opening a div and giving it a class of headingcontainer using the html_writer.
$content .= html_writer::empty_tag('img', array('src'=>$this->pix_url('headingpic', 'theme'), 'alt'=>'', 'class'=>'headingimage'));
Again we are using the html_writer to do the work, however this time we are producing an img tag. You'll also notice the use of the pix_url function. Because our renderer is extending the core_renderer we can make use of all of its functions, pix_url being a useful one in this case.
$content .= parent::heading($text, $level, $classes, $id);
Of course the other thing we can do in this case is still use the core_renderers heading method to produce the h* tag as we are just adding content around it.
$content .= html_writer::end_tag('div');
The final thing to do is close the div that we opened on line one. Again we can do this with the html_writer.
Before testing it there are a few more things we need to do:
1. Create an image called headingpic.png and put it into a directory called pix located within your theme directory - /theme/overridetest/pix/headingpic.png
2. Create a CSS file called overridetest.css within a directory called style within your theme directory - /theme/overridetest/style/overridetest.css
3. Add into overridetest.css the following two lines of css.
.headingcontainer .headingimage {float:left;margin-right:1em;}
.headingcontainer h2,
.headingcontainer h3,
.headingcontainer h4,
.headingcontainer h5,
.headingcontainer h6 {text-align:left;}
4. Add the following line to the bottom of the config.php file
$THEME->sheets = Array('overridetest');
With that done if you now view a page within your Moodle installation you should see the difference.
As you can see from the clipping above heading now have our image next to the text and are left aligned.
Finding renderers to override
Identifying renderers and locating the files where they are located is necessary in order to override them.
Renderers should always be located in a file ending with renderer.php.
The renderer.php file should always be located in the base directory of a component, e.g.
- /webservice/renderer.php
- /mod/forum/renderer.php
- /mod/workshop/allocation/manual/renderer.php
or in the <plugindir>/classes/ folder when used with autoloading.
The only exception to this is the core renderer which is located in /lib/outputrenderers.php.
There is also a routine way to name renderers much like the process for naming a theme's overriding renderer.
It should always be made up of the following three components joined by underscores:
- component This is the component the renderer belongs too, e.g. block, mod, or core.
- name This is the name of what ever the component is, e.g. forum, workshop, or wiki.
- renderer They should always be completed by the word renderer.
The following are examples of renderers currently in use within Moodle:
class core_webservice_renderer {}
class mod_forum_renderer {}
class workshopallocation_manual_renderer {}
Again the only exception to this is the core renderer which is just core_renderer.
Overriding a component's renderer
Overriding a component's renderer works almost exactly the same as overriding a core renderer.
As a matter of fact there are quite some renderers that also have 'core' in their name, which can be a bit confusing.
One pitfall is that, in order to extend such a renderer, it's class definition must be loaded first (unless you use autoloading).
Below is an example of how to override the calendar renderer.
1. Within your renderers.php as created above add the line that loads the calendar's core_calendar_renderer class definition
<?php
class theme_overridetest_core_renderer extends core_renderer {
}
include_once($CFG->dirroot . "/calendar/renderer.php");
2. Add the theme_overridetest_core_calendar_renderer class definition that extends core_calendar_renderer
<?php
class theme_overridetest_core_renderer extends core_renderer {
}
include_once($CFG->dirroot . "/calendar/renderer.php");
class theme_overridetest_core_calendar_renderer extends core_calendar_renderer {
// place your overridden methods (functions) here.
}
Note again:
- The name of the renderer is VERY important it is made up of three sections joined by underscores:
- theme: This will always be the same when overriding renderers from the theme.
- overridetest: This is of course the name of our theme, and should be the name of the theme the renderer is being overridden from.
- core_calendar_renderer: This is the renderer that we are overriding.
- The renderer should always extend the renderer it is overriding. This ensures we don't need to override every method the renderer offers.
With the theme_overridetest_core_calendar_renderer class defined you can now override the methods that need overriding.
For example to stop the button to add an event from being rendered:
<?php
class theme_overridetest_core_renderer extends core_renderer {
}
include_once($CFG->dirroot . "/calendar/renderer.php");
class theme_overridetest_core_calendar_renderer extends core_calendar_renderer {
/**
* Disabled creation of the button to add a new event (Was: Creates a button to add a new event)
*
* @param int $courseid
* @param int $day
* @param int $month
* @param int $year
* @return string
*/
protected function add_event_button($courseid, $day=null, $month=null, $year=null) {
return '';
}
}
The same principle applies for all other renderers.
Namespaces
Renderers can also be used with code that uses namespaces. When creating or overriding new renderers with namespaces, be sure to use "output" as the second level namespace to comply with the rules for namespaces. Non-namespaced renderers can be overridden by a namespaced renderer and vice-versa - this leads to a complex search strategy when looking for overridden renderers.
For example: the renderer for the component "mod_assign" with subtype "custom" and target "cli" could exist in any of these classes (listed in priority order):
- theme_child\\output\\mod_assign\\custom_renderer_cli
- theme_child\\output\\mod_assign\\custom\\renderer_cli
- theme_child_mod_assign_custom_renderer_cli
- theme_parent\\output\\mod_assign\\custom_renderer_cli
- theme_parent\\output\\mod_assign\\custom\\renderer_cli
- theme_parent_mod_assign_custom_renderer_cli
- mod_assign\\output\\custom_renderer_cli
- mod_assign\\output\\custom\\renderer_cli
- mod_assign_custom_renderer_cli
- theme_child\\output\\mod_assign\\custom_renderer
- theme_child\\output\\mod_assign\\custom\\renderer
- theme_child_mod_assign_custom_renderer
- theme_parent\\output\\mod_assign\\custom_renderer
- theme_parent\\output\\mod_assign\\custom\\renderer
- theme_parent_mod_assign_custom_renderer
- mod_assign\\output\\custom_renderer
- mod_assign\\output\\custom\\renderer
- mod_assign_custom_renderer
For more examples of possible class names for renderers see lib/tests/outputfactories_test.php