How do I create a new renderer?

How do I create a new renderer?

by David Scotson -
Number of replies: 16
I'd like to create a new renderer that can be called by other renderers when they want to create forms/inputs/buttons etc. (MDL-40181)

Does anyone know how to go about this?

I've read this page but I'm still a bit confused:

http://docs.moodle.org/dev/Output_renderers

I just want some functions that you give values to and get back the appropriate HTML, and which can be overridden to deliver slightly different HTML. How do I tie that in with Moodle's renderer system?

edit: I recalled that tabs had been moved into a renderer recently in MDL-38309, but in that case they'd just got moved into the core_renderer. I suppose I could just do that for the time being but it seems a bit more elegant to have them in a dedicated class.
Average of ratings: -
In reply to David Scotson

Re: How do I create a new renderer?

by David Scotson -
Possibly related sub-question: if I have a small library of theme related functions that I want to use in my theme, but might also be useful for child themes where would I put that?

For example, Bootstrap does alerts in a certain way. There's multiple renderers where I might want to use that same code. So I put it in a class and call it in my Bootstrap theme's renderers. That all works fine.

However, if someone wants to override my theme in a child theme, but still wants to do Bootstrappy things they might also want to use that function, or even slightly tweak that function so that it does something different. How do I structure things so that it's easy for them to do so?

The renderer structure seems like it's in the same area, but it also seems quite tightly tied to the idea of a page, rather than just some functions you call from a library.


In reply to David Scotson

Re: How do I create a new renderer?

by Gareth J Barnard -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Plugin developers

Dear David,

To be honest I'm not sure on both accounts pertaining to Moodle.  But from an OO point of view, it's a matter of defining them in a common area and making the method's you want to be overrideable to be 'protected'.  I hope I'm not telling you to suck eggs.

Or should your functions be within 'renderer.php' as additions to the class?

Cheers,

Gareth

In reply to David Scotson

Re: How do I create a new renderer?

by Paul Nicholls -

Hi David,

Theme renderers are inherited, so unless they override them, the child themes will automatically get them.  I say this from my experience with Decaf - it uses renderers, and child themes pick them up automatically but can override them if necessary.  I make use of this for the theme we're actually using here, which is a child of Decaf.

For example, Decaf has:
class theme_decaf_core_renderer extends core_renderer {
and our child theme (uc_decaf) has the following:
class theme_uc_decaf_core_renderer extends theme_decaf_core_renderer {

The child theme's renderer only bothers to override one function, leaving the rest to inherit from theme_decaf_core_renderer.

 

HTH,
Paul

In reply to David Scotson

Re: How do I create a new renderer?

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Having a library of functions that other people can reuse is really nothing special. That is what programming is all about.

So, just create a file like theme/mytheme/utils.php, and put the functions/classes in there. If anyone else wants to use those functions, they can requie_once them, and then call them.

Or, looking ahead to what will happen in Moodle 2.6, group all library into classes, for example a class theme_mytheme_utils stored in theme/mytheme/classes/utils.php. For now you still have to require_once it, but in future, people will be able to use it without an explicit require_once. See https://moodle.org/mod/forum/discuss.php?d=229474.

Of course, it is possible that you might be going to implement a really singificant library of code, that you think does not really belong inside your theme. In that case you could put it in a separate plug-in like local/mycoollib.

Average of ratings: Useful (2)
In reply to David Scotson

Re: How do I create a new renderer?

by Mary Evans -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

Marina Glancy did quite a bit of work with Course renderers. so may be an idea to see how she did it, by looking at the code she made into a function which was then set into course/renderer.php

In reply to David Scotson

Re: How do I create a new renderer?

by Mary Evans -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

At present you can only override a renderer. And no need to tell you hw to do that as you know full well how to. But as for starting from scratch, I don't have a clue, but I suspect it's quite involved, and needs certain manipulation of core files pertinent to the renderer you want to create.

Sam Marshal and Tim Hunt would probably give you some pointers, as would Marina Glancy if you were to ask theme.

HTH

Mary

In reply to David Scotson

Re: How do I create a new renderer?

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Probably the best example of someone doing this is when sam marsall moved the media player code into a renderer. Let me find that ...

The key bit of that is the new renderer at https://github.com/moodle/moodle/commit/daefd6eb9736473720749af4f2bff8ee1ae2a3e7#L12R2915.

By the way, if you don't know about that, then you might want to take a look. It lets you completely change which media player is used from within your theme.

So, having given a particular example, let me revise the theory of how renderers are supposed to work:

1. The purpose of a renderer is to "some functions that you give values to and get back the appropriate HTML, and which can be overridden to deliver slightly different HTML" - so David is definitely asking the right question.

2. A renderer is defined in one place, and then overridden by (parent or child) themes.

3. The place that defines a renderer is a plugin, or moodle core. In fact, a plugin can define several different renderers if it likes. For example, we have core_renderer, core_media_renderer and core_question renderer. Quiz has only mod_quiz_renderer at the moment, but at some point in the future we might add another renderer like mod_quiz_editing_renderer just to keep the code for the teacher UI separate from the student UI.

4. Deciding whether to put all the methods in one renderer, or break it up into smaller logical renderers is really not a big deal. To use the example already given, when tabs were converted to be a renderer, then it was put into core_renderer, but equally it could have been put into a separate renderer core_tabs_renderer. That would work equally well.

5. Having said it really does not matter whether you make one big renderer, or several smaller renderers, each focussed on a particular bit of functionality, I think I would recommend more smaller renderers, to keep specific functionality grouped better. However, this is a matter of taste.

OK, I think that is enough background theory, but now we get to the pay-off:

6. A theme is a type of plugin. Therefore, a theme can define its own renderer. That would go into theme/example/renderer.php and it would be called class theme_example_renderer extends plugin renderer. Of course, as recommended above, you might want to generate several renderers for you theme, like theme_example_prettyswirls_renderer and theme_example_slickstripes_renderer, and you can.

7. If you define that renderer, then you can use it, for example in your layout/general.php file:

$renderer = $PAGE->get_renderer('theme_example');
echo $renderer->thingy($values);

or

$renderer = $PAGE->get_renderer('theme_example', 'prettyswirls');
echo $renderer->clockwise_swirl($values);

You might also want to use you renderer within another renderer that you have overridden. For example

class theme_example_mod_quiz_renderer extends mod_quiz_renderer {

public function view_page($values) {

$renderer = $this->page->get_renderer('theme_example');
$output = '';
$output .= $renderer->border_start($values);
$output .= parent::view_page($values);
$output .= $renderer->border_end($values);
return $output;

}

}

8. And like any other renderer, themes could override it, in which case they might define class theme_child_theme_example_renderer extends theme_example_renderer. That class would be defined in theme/child/renderers.php.

Have I made that clear? If not, please ask.

Average of ratings: Useful (3)
In reply to Tim Hunt

Re: How do I create a new renderer?

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Just in case that was clear, and you are not yet confused, can I point out a theoretic, but probably totally useless possibility:

It is not just theme_child that could override a renderer defined by theme_example. theme_example is allowed to override its own renderers. So, if you define

In theme/example/renderer.php

class theme_example_renderer extends plugin_renderer_base {
// ...
}

Then,

In theme/example/renderers.php you can define

class theme_example_theme_example_renderer extends theme_example_renderer {
// ...
}

As I say, this is almost entirely useless, but it is a test of whether you really understand how renderers work tongueout 

Average of ratings: Useful (2)
In reply to Tim Hunt

Re: How do I create a new renderer?

by Mary Evans -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

I'm still following smile

In reply to Mary Evans

Re: How do I create a new renderer?

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Obviously I must try harder to be confusing.

I am now waiting for someone to create theme_theme_theme_theme_theme_renderer.

In reply to Tim Hunt

Re: How do I create a new renderer?

by Mary Evans -
Picture of Core developers Picture of Documentation writers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

Tim,

Would it be possible to create a renderer that changed the Home link in the Navbar/breadcrumb do something else, like go to a sister site? If so where would I start?

I've been searching Moodle using git grep and have found lots of interesting things but nothing that gives me the HTML that forms the Home link in the navbar.

Cheers

Mary

In reply to Mary Evans

Re: How do I create a new renderer?

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

I don't think you would create a renderer to solve that problem.

Remember: a renderer is "some functions that you give values to and get back the appropriate HTML".

Changing the destination of the Home link is not that.

However, you can abuse renderers to change the destination of the home link. Something like:

class theme_hacky_core_renderer extends core_renderer {
    public function navbar() {
        global $PAGE;

        $out = parent::navbar();
        $out = preg_replace( ... , $out);
        return $out;
    }
}

(There are other, even more hacky ways to subvert the navigation stuff from a renderer.)

In reply to Tim Hunt

Re: How do I create a new renderer?

by David Scotson -
That's really great info Tim, thanks. I'll have a go porting some of the stuff I've just done as simple classes to this format, inside a theme at renderer (or two) first.