Output renderers
Note: This page is a work-in-progress. Feedback and suggested improvements are welcome. Please join the discussion on moodle.org or use the page comments.
Output renderers | |
---|---|
Project state | Implemented |
Tracker issue | MDL-21235 |
Discussion | n/a |
Assignee | Petr Škoda (škoďák) + David Mudrak + feedback and ideas from other developers |
Moodle 2.0
Goals
- stable API
- easy to use
- easy to customise via themes
Renderers
Output renderer is a class with collection of methods that handle rendering of visual aspects of Moodle pages, emails, html export, etc. In Moodle 1.9, general output related functions were located in weblib.php and modules stored rendering code in lib.php, locallib.php, view.php, etc. Output renderer instances are obtained through moodle_page::get_renderer($component, $subtype = null, $target = null) method.
The general renderer class naming convention is:
{theme name}_{plugin type}_{plugin_name}_{subtype}_renderer_{target}
or (if using namespaces)
\{plugin type}_{plugin name}\output\{subtype}_renderer_{target} (standard plugin renderer)
\theme_{theme name}\output\{plugin type}_{plugin name}\{subtype}_renderer_{target} (plugin renderer overridden in a theme)
where theme name prefix, subtype infix and target suffix are optional. Examples of renderer class name are core_renderer, mytheme_core_renderer, mod_workshop_renderer, mod_forum_renderer_cli, \tool_templatelibrary\output\renderer etc.
- Renderer subtype
- When get_renderer() is called, $subtype parameter can be provided to obtain a different renderer. Renderer subtypes are used mainly for core subsystems, but it is also possible to use them in modules.
- Renderer targets
- In most cases, we are rendering page output for web browsers. Sometimes we need to generate different output for command line scripts, email messages, etc. When get_renderer() is called, $target parameter can be provided to obtain a different renderer. The list of targets will be part of the specification, individual plugins should not invent new rendering targets.
- note that both $subtype and $target works similarly - they just influence the name of the class to be returned by get_renderer(). But there is semantic difference and we will probably have the list of allowed targets limited (defaults to html).
Overall picture
The following UML diagram describes the main classes involved in HTML output rendering and their relationships.
renderer_base
Abstract class every other renderer must extend. Renderer base implements basic methods and support for renderer dispatching.
core_renderer
Current core_renderer is available through the global $OUTPUT variable. This global should not be used in lower level APIs - only in the main scripts like view.php. When you want to start to print something, you will probably call
echo $OUTPUT->header();
in your view.php or similar script. To print a page footer and terminate your PHP script, you will call
echo $OUTPUT->footer(); die();
Beside these page header and footer related things, core_renderer knows how to display
- boxes and containers - basically <div> elements
- general widgets - headings, single buttons, selection forms, arrows in the breadcrumb navigation, language selection popup, user login info etc.
- error messages, notifications
- blocks
method render() and interface renderable
Various widgets (headings, buttons etc.) and other content blocks (submissions, forum posts etc.) are represented as classes. In most cases, they will look like a simple object with just some properties and a constructor to set the most common ones. Core renderer implements protected methods to render these widgets. These widget rendering methods are called render_something() where something is the name of the widget. Their first parameter must be an instance of something class that implements interface renderable. These methods can be overridden by themes.
Public method to render a widget is called render(). It accepts the only parameter which must be an instance of a class implementing renderable interface. It uses the class name of the widget passed in as parameter to find the corresponding protected rendering method.
Implementation of renderer_base::render() (inherited by all renderers)
class renderer_base {
// ...
public function render(renderable $widget) {
$rendermethod = 'render_'.get_class($widget);
if (method_exists($this, $rendermethod)) {
return $this->$rendermethod($widget);
}
throw new coding_exception('Can not render widget, renderer method ('.$rendermethod.') not found.');
}
// ...
}
For some widgets, a helper method may be implemented to instantiate the widget and render it in the background. Such helper methods have the same name as the widget class and accepts parameters needed to create the widget instance. Themes should not override helper methods because their usage is optional.
Example: User avatar rendering involves following methods and classes:
class core_renderer extends renderer_base {
// ...
protected function render_user_picture(user_picture $userpicture) {
// returns HTML code to be echoed for displaying the user avatar
}
public function user_picture(stdclass $userrecord, array $options=null) {
// helper method that constructs $user_image widget and calls $this->render()
$picture = new user_picture($userrecord, $options);
return $this->render($picture);
}
// ...
}
class user_picture implements renderable {
public $userrecord;
public $courseid = 0;
public $link = true;
public function __construct(stdclass $userrecord, array $options=null) {
// ...
}
}
There are two ways a developer can render the user picture. Either using a one-line style:
$user = $DB->get_record('user', array(...));
echo $OUTPUT->user_picture($user, array('courseid' => $courseid, 'link' => true));
or an explicit stye:
$user = $DB->get_record('user', array(...));
$avatar = new user_picture($user);
$avatar->courseid = $courseid;
$avatar->link = true;
echo $OUTPUT->render($avatar);
The point is that the first style is usually one line only, it is also possible to have multiple helpers with different parameters. The second style allows more options without cluttering the simple API with tons of optional method parameters.
Plugins can also use renderable objects and define render_something() functions in plugin renderers.
core_renderer_cli
_cli suffix indicates this is a core renderer for CLI rendering target (CLI scripts). For example headings are using => instead if H2 html tag.
Core subsystem renderers
Plugin renderers
Plugin renderers are stored in renderer.php file in the same folder as the lib.php file of each plugin. They all extend plugin_renderer_base method.
plugin_renderer_base
An important thing about this class is that it keeps a reference to an instance of (typically) core_renderer which is used as an underlying renderer. This reference is kept in protected $output member variable. So, there is no more $OUTPUT in plugin renderers. They all use $this->output->something() to render the output.
Example: Workshop module defines mod_workshop_renderer class in mod/workshop/renderer.php file. The renderer defines a method to display a student submission.
// in renderer.php:
class workshop_submission implements renderable {
public function __construct(stdclass $submission, $anonymous = false, array $attachments = null) {
// here the widget is prepared and all necessary logic is performed
if ($anonymous) {
$this->authorname = get_string('anonymousauthor', 'workshop');
} else {
$this->authorname = fullname(...);
}
}
}
class mod_workshop_renderer extends plugin_renderer_base {
protected function render_workshop_submission(workshop_submission $submission) {
$out = $this->output->heading(format_string($submission->title), 2);
$out .= $this->output->container(format_string($submission->authorname), 'author');
$out .= $this->output->container(format_text($submission->content, FORMAT_HTML), 'content');
return $this->output->container($out, 'submission');
}
}
// in view.php
$output = $PAGE->get_renderer('mod_workshop');
$submissionrecord = get_record('workshop_submissions', array(...));
$submissionwidget = new workshop_submission($submissionrecord, false);
echo $output->header();
echo $output->render($submissionwidget);
echo $output->footer();
Note that $output->header() and $output->footer() are not defined in mod_workshop_renderer. Plugin renderers are able to forward calls to the underlying renderer automatically. So, in view.php, there could be echo $OUTPUT->header(); which would call the same method. We prefer the way above because of consistency - there is the only one $output instance used instead of two $OUTPUT and $wsoutput.
WARNING: get_renderer() should typically be the last output related call you make before displaying your page's header. get_renderer() forces the page layout to 'base' if it has not already been set and this will almost certainly cause your page to display incorrectly (e.g. no blocks).
Note that as of Moodle 2.9, the render method could be rewritten to use a template. See Templates for more information.
Bootstrap renderer
Bootstrap renderer is used as a temporary $OUTPUT before the full initialisation of standard core renderer.
Theme customisations
Theme renderers
Theme renderers are stored in renderers.php file in theme folder (or autoloaded from the classes folder). The actual overriding of renderers is the responsibility of renderer_factory classes which may be selected in theme config.php.
Creating a Renderable "widget" in your theme
To create a renderable "widget" that could be used in multiple places in your theme (e.g. on the front page, in an overriden renderer for mod_assign etc.)
Create a renderable and renderer in the following folder:
/theme/mytheme/output/mywidget/rendererable.php
theme/mytheme/output/mywidget/renderer.php
$mywidgetrenderable = new \theme_mytheme\output\mywidget\renderable($params);
$mywidgetrenderer = $this->page->get_renderer('theme_mytheme','mywidget'); // Or $PAGE->get_renderer depending on context
$mywidget = $mywidgetrenderer->render($mywidgetrenderable);
there is a good example of this in moodle core -> /admin/tool/monitor
Low level HTML output
Low level html markup is created via html_renderer class.
See also
- Templates
- Theme changes
- Using Moodle Totally confused by output renderers... forum discussion