Note:

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

Output renderers: Difference between revisions

From MoodleDocs
Line 53: Line 53:
# blocks
# blocks


=== method render() ===
=== 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.
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.
Line 59: Line 59:
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.
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.


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 that want to change how widget is displayed should override function render_something() and should not override helper methods because their usage is optional.
Implementation of renderer_base::render() (inherited by all renderers)
<code php>
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.');
    }
    // ...
}
</code>
 
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:
'''Example:''' User avatar rendering involves following methods and classes:
<code php>
<code php>
class core_renderer extends renderer_base {
class core_renderer extends renderer_base {
    public function render(renderable $widget) {
        // if $widget is instance of user_image, then call $this->render_user_image($widget)
    }
     // ...
     // ...
     protected function render_user_picture(user_picture $userpicture) {
     protected function render_user_picture(user_picture $userpicture) {
         // returns HTML code to be echoed for displaying the user avatar
         // returns HTML code to be echoed for displaying the user avatar
Line 79: Line 88:
         return $this->render($picture);
         return $this->render($picture);
     }
     }
 
    // ...
  // ...
}
}


class user_picture implements renderable {
class user_picture implements renderable {
  public $userrecord;
    public $userrecord;
  public $courseid = 0;
    public $courseid = 0;
  public $link = true;
    public $link = true;


  public function __construct(stdclass $userrecord, array $options=null) {
    public function __construct(stdclass $userrecord, array $options=null) {
      // ...
        // ...
  }
    }
}
}
</code>
</code>

Revision as of 02:45, 15 April 2013

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}

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, 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.

uml output renderers.png

(dia source)


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

  1. boxes and containers - basically <div> elements
  2. general widgets - headings, single buttons, selection forms, arrows in the breadcrumb navigation, language selection popup, user login info etc.
  3. error messages, notifications
  4. 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 how 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.

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. The actual overriding of renderers is the responsibility of renderer_factory classes which may be selected in theme config.php.

Low level HTML output

Low level html markup is created via html_renderer class.

See also