Themes

 
 
It's only an avatar...
All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful MoodlersGroup Testers

Can someone point me in the 'write' direction to more about the HTML Writer, syntax and stuff? I first came across html_writer in a tutorial written by Sam Hemelric, but he only gave a brief overview of it in this page...

All about html_writer

Thanks

Mary

 
Average of ratings: -
Tim at Lone Pine Koala Sanctuary
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

Just search the Moodle code for "html_writer::" to find lots of examples.

It is basically a simple helper class for outputting HTML tags. It lets you do things like

echo html_writer::tag('div', 'Some content', array('id' => 'xyz', class => 'mydiv somethingelse'));

Of course, for a simple example like this, it would be easier to do

echo '<div id="xyz" class="mydiv somethingelse">Some content</div>';

however, in code where the content and the attributes are coming from different variables and function calls, html_writer is a lot easier to use that writing code with lots of string concatenation.

 
Average of ratings:Useful (1)
It's only an avatar...
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful MoodlersGroup Testers

Hi Tim,

Thanks for the reply.

I have used html_writer to good advantage in a couple of themes, lately, and understand what I have done so far, but there are elements of the syntax I am struggling with. Mainly not knowing how to write the following...

in simple HTML terms it goes like this...

<a href="localhost/moodle" title="home" ><img src="home_icon.png" alt="Home" /></a>

However, how would I write the above in php using the html_writer class for Moodle 2.0, given that there are some predefined php functions for pix output?

For example - hyperlinks to specific pages can be written thus...

echo html_writer::link(new moodle_url('/user/profile.php', array('id'=>$USER->id)), get_string('myprofile')).' | ';
echo html_writer::link(new moodle_url('/login/logout.php', array('sesskey'=>sesskey())), get_string('logout'));

and output for an image can be written thus...

echo html_writer::tag('div', $OUTPUT->user_picture($USER, array('size'=>55)), array('class'=>'userimg'));

But how would I combine the two to get my example to work?

Mary

 
Average of ratings: -
Tim at Lone Pine Koala Sanctuary
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

Try

echo html_writer::link(new moodle_url('/'),

html_writer::empty_tag('img', array(

'src' => $OUTPUT->pix_url('home_icon', 'theme'),

'alt' => get_string('home'))),

array('title' => get_string('home')));

It would be nice to add an html_writer::image($src, $alt, $attributes); method, but no ones seems to have written that yet. So, we just have to use html_writer::empty_tag, which is easy enough.

 
Average of ratings:Useful (2)
It's only an avatar...
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful MoodlersGroup Testers

Thanks for this Tim!

 
Average of ratings: -
Picture of Matt Gibson relaxing in the Alps
Re: All about html_writer
 

Is it the intention that themes can alter the behaviour of the HTML writer?

 
Average of ratings: -
It's only an avatar...
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful MoodlersGroup Testers

No...rather the other way round.

I just want to know HOW to USE it to my advantage.

 

 
Average of ratings: -
Tim at Lone Pine Koala Sanctuary
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

There are two levels of abstraction here. Renderers and html_writer.

Renderers are the higher level concept. They have methods like $OUTPUT->user_picture(...), $OUTPUT->block(...), $questionrenderer->question(...). These are the things that themes can override.

Obviously, the renderers actually need to output HTML in order to display those things, and html_writer is just a low-level helper class that makes that easier. For example, it is probably easier to say

echo html_writer::empty_tag('img', array('src' => $pixurl, 'alt' => get_string('alt')));

than it is to say

echo '<img src="' . $pixurl . '" alt="' . get_string('alt') . '" />';

Actually, in that simple case, the string concatenation comes out shorter than the html_writer call. In more complex cases, however, the string concatenation gets worse, and very hard to read. The html_writer version tends to stay more manageable.

 
Average of ratings: -
It's only an avatar...
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful MoodlersGroup Testers

Hi Tim,

Perhaps if I had asked for a Glossary of Terms so that I could learn the various terms used as you have in this example:

echo html_writer::empty_tag('img', array('src' => $pixurl, 'alt' => get_string('alt')));

Go back a few years and try to recall how you first learnt how to do this.  All I am doing at the moment is finding something similar and copying it. Surely there must be a book I can read on this...but what do I ask for at Waterstones or on Amazon!

Thanks

Mary

 
Average of ratings: -
Picture of Richard Oelmann
Re: All about html_writer
Group Particularly helpful MoodlersGroup Testers

+1 for that

I'm looking at all this and thinking why? It seems at the moment, that it's easier to just write the code by hand than dig so deep to find examples of code to try to learn from with no real guide - and when I'm really still looking for examples that make the html_writer more worth while - and easy enough for others to follow behind and build on any code I've created - than coding by hand. I personally don't like the idea of writing part of the code with html_writer and part of it 'traditionally' and if it's the best/recommended way of doing it I would like to use the html_writer, but want to do it properly smile

Are the performance hits of calling html_writer for something like <li> or an img tag like your example, Tim, outweighed by the benefits for the relatively few complex lines used in theming? Or is this something which is likely to be appropriately used more in the core coding or the modules and plugins?

Please don't take this as criticism of the html-writer class, it is purely intended to help me understand the benefits of it and how to use it best.

Richard

 
Average of ratings: -
Tim at Lone Pine Koala Sanctuary
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

Well, html_writer did not exist a few years ago wink

The way to learn the details of an API like html_writer is to look at the documentation of the methods (http://phpdocs.moodle.org/HEAD/core/lib/html_writer.html#empty_tag - warning, that is a bit out of date) and then if that is not explained well enough, just try it out and see what happens, or read the code.

 
Average of ratings: -
It's only an avatar...
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful MoodlersGroup Testers

Thanks Tim, this will come in very useful.

Cheers

Mary

 
Average of ratings: -
Tim at Lone Pine Koala Sanctuary
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

Just be warned that phpdocs.moodle.org, which should be automatically regenerated regularly, was last updated in August. That is very out-of-date. I just reopened MDLSITE-952. Please vote if you care.

 
Average of ratings: -
Picture of Richard Oelmann
Re: All about html_writer
Group Particularly helpful MoodlersGroup Testers

Excellent Tim, thanks for that

Richard

 
Average of ratings: -
Picture of Stuart Lamour
Re: All about html_writer
Group Particularly helpful Moodlers

finding quite a few cases where the html_writer seems to be being used for things that are both shorter and more readable to do in html.

course/lib has things like echo html_writer::start_tag('div', array('class' => 'activityinstance')); 

renderers are also full of people using $content.=html_writer::start_tag('ul',array('class'=>'nav')); and other crazy stuff.

any ideas how we can nip this in the bud?

 
Average of ratings: -
Picture of David Scotson
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers
I never managed to wrap my head around html_writer. I tried (and filed a couple of bugs, like MDL-35307) but just couldn't cope with it. The amount of extra typing involved makes it very hard for me to read code using it, and the first thing I now do when trying to figure out what a renderer is actually doing is go through and manually translate all the html_writer::tags to something that I can read and map mentally back and forth between code and the output it produces.

I'm struggling for something constructive and useful to say since this is planted fairly deep and I don't think there's much chance of change. Let me see..., okay here goes but bear in mind this is from a programmer point of view, I realise it's tricky to be everything to everyone:

1. I think it would be better if the name html_writer was as short as possible e.g. out::tag, html::tag, h::tag tag::div as it's used all over the place so like common words in languages it should be short (same for get_string, which I've seen elsewhere as a single or double underscore e.g. _("hello")). It would be fairly easy to point both at the same function for a transition period.

2. It would be better if you didn't pass the tag name as a string as it allows difficult to catch typos, like html_writer::tag('dib', $etc. If it's html_writer::div($etc. then not only do you get some extra typo checking, but you've saved yourself three characters, two apostrophes a comma and a space (at a cost of an extra function calll, which PHP developers generally seem to be allergic to).

3. You shouldn't be using such low-level tag writing most of the time. If you're outputting a cancel button you should be calling a function and giving it the unique info it needs e.g. out::cancel_button($text) and all the muddling about with HTML should be hidden within that function, that means if you need to change all the cancel_buttons to have a different tag or class or wrapper or whatever you can do it in one place. This is very related to Amy Groshek's attempt to map out the disparate HTML used in Moodle and somewhat standardise it (see https://moodle.org/mod/forum/discuss.php?d=216519 for more on that and Danny Wahl's comment in particular: https://moodle.org/mod/forum/discuss.php?d=216519#p942924 is relevant to this). That encapsulation should carry on all the way up the stack, so if you need a warning dialog you should just give it the text, and it'll take care of calling the ok and cancel buttons which will then allow all dialogs to look and act the same and be themeable.

4. I don't know if it's too late for this, but as in my bug linked above, the one thing I really couldn't cope with was the attributes going at the end. My poor little brain just couldn't manage going from <div class="first">content</div> to html_writer::tag('div', $content, array('class'=>'first')) and back, especially when the $content was another set of tags.

I had a look to see if there was a widely used PHP library for this kind of thing but the only thing I found was twig templates which seemed a bit much for my own use.
 
Average of ratings: -
Picture of Danny Wahl
Re: All about html_writer
Group DevelopersGroup Particularly helpful Moodlers

I've been trying to figure out why to use html_writer, and really I can only come up with because it's been decided to.

As to why it was made in the first place, I'm not sure.  I think a programmer decided that HTML just wasn't complex enough.  So we took something that was cascading and made it object-oriented.

I'm just waiting for them to finish css_writer so I can do this:

$css = css_writer::rule($color, 'background-color', '#page-header');

I kid, I kid...

 
Average of ratings: -
Tim at Lone Pine Koala Sanctuary
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

Do you really think code like https://github.com/timhunt/moodle/blob/MOODLE_19_STABLE/question/type/questiontype.php#L931 is easy to work with?

How easy it is to convince youself that will always output valid HTML? The equivalent html_writer code is guaranteed to output valid HTML, with all argument values properly escaped, and all attributes correctly surrounded by quotes.

The corresponding code in 2.x is not exactly the same, because of other changes in the UI but it at https://github.com/timhunt/moodle/blob/master/question/engine/renderer.php#L287.

Also, in the days of XHTML, the recommendation was to write empty tags like <img ... />. Now with HTML5 the recommendation is to write <img ...>. If you have raw HTML throughout your code, making that change is hard. If you are using a funciton like html_writer::empty_tag everywhere, you only have to change one line of code.

 
Average of ratings: -
Picture of Danny Wahl
Re: All about html_writer
Group DevelopersGroup Particularly helpful Moodlers

I'm not entirely sold that the 1.9 version is "harder" to properly escape than the 2.0 - the benefit of the 2.0 version is debugging, or fatal error...

In the end, I wasn't really ragging too hard on html_writer.  It's not that complex, or that hard to manage, but if you just take the face value of a tag and compare it to html_writer - it seems overly engineered.

Just realized I forgot this on my last post:

html_writer::tag('sarcasm', $content, array('class'=>'none'));

 
Average of ratings: -
Picture of Stuart Lamour
Re: All about html_writer
Group Particularly helpful Moodlers

@danny -my suggestion to improve the syntax would be to have, like in the forms lib, a html_writer of type html

e.g.

html_writer::html("<sarcasm class='none'>$content</sarcasm>");

this would be of great benefit.

 

 

 

 

 
Average of ratings: -
Picture of David Scotson
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers
For the same reasons of changing in one place, it would be good if "links with icons" were created in one place, rather than built from scratch each time, then that code snippet could look something like:

 
 return html::div('editquestion', html::icon_link($editurl, 'edit', _('editquestion', 'question')));
 


Or if the wrapper div is standard (it's not really clear from just looking at this one) then it too could be written out within the standard function)

 
 return html::icon_link('editquestion', $editurl, 'edit', _('editquestion', 'question')));
 


As I said, I realise this is a more programmer-y way to think, HTML coders might think where do I figure out what the parameters are etc., but you don't really get the ability to change and theme without that level of encapsulation. Maybe if those functions were "templates" it might make more sense though I think once you start getting into small modular components the line between PHP functions and re-usable templates is fairly blurred.
 
Average of ratings: -
Picture of David Scotson
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

An actual example that I've found in various renderers:


foreach ($nextstageurl->params() as $key=>$value) {
            $form .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$key, 'value'=>$value));
        }
 

Where the code is basically a verbose version of the output HTML:


<input name=contextid type=hidden value=2>
<input name=filepath type=hidden value=318aeb66dba730135ab93ba66f9dbea3>
<input name=stage type=hidden value=2>
 

And could have probably just as easily been written:


foreach ($nextstageurl->params() as $key=>$value) {
            $form .= "<input type=hidden name=\"$key\" value=\"$value\">";
        }
 

But since we've already introduced the PHP function and the concept of looping, and hidden inputs are fairly standard things I'd rather see:


html::hidden_inputs($nextstageurl->params());
 

or for a single input:


html::hidden_input($name, $value);
 

even though it might initially be a bit more opaque, it's much more readable once you understand it, and you can get a much better overview of what's going on rather than everything being hidden by the low-level details. And then you can get even more abstract and just pass the params into the function building the form, or just pass the moodle_url object that actually has both the URL and the params within it rather than extracting them out and working on them directly every time.

 
Average of ratings: -
Tim at Lone Pine Koala Sanctuary
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

Did you look to see whether this already existed? https://github.com/moodle/moodle/blob/master/lib/outputcomponents.php#L1427

 
Average of ratings: -
Picture of David Scotson
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers
I think I had actually seen that function. I skimmed through the class as I was writing a replacement for it as even though I didn't like the API and/or wanted to have tests that I could run independently to allow me to reshape the API as I went, the functionality required was documented there.

That version takes a moodle_url. A grep for the 17 uses of this function in Moodle reveals 2 of those are creating a moodle_url from an array just so that the array can be pulled back out of the object by that function. My version just takes a plain PHP array. There's also no single hidden input version, in which case you'd need to create a single item array as well as a moodle_url object to use this version. I would consider this code below to be as confusing as the code quoted above though for different reasons. It's misleading about what's going on as it would appear on first reading that a moodle_url is required by the function and that $PAGE->url was going to be used in some way, and who knows what kind of exciting things could be happening in the moodle_url constructor:

 
echo html_writer::input_hidden_params(new moodle_url($PAGE->url, array('sesskey' => sesskey(), 'return' => $return)));
 


Arrays are probably the best bit of PHP, using them for params is almost a perfect match. Passing a moodle_url into something that was creating the whole form on the other hand, and was going to use both the url and the parameters makes more sense.

But my point was more that code in this style wasn't being used, the fact that a library function exists and it is still not being widely used isn't exactly an improvement for anyone reading that code.
 
Average of ratings: -
Tim at Lone Pine Koala Sanctuary
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

I agree. I wish there was a single hidden input method, and also a hidden_inputs method that just took an array. Someone should code it and submit a patch.

 
Average of ratings: -
Picture of Stuart Lamour
Re: All about html_writer
Group Particularly helpful Moodlers

@tim - for me the 1.9 example is much easier to work with.

It highlights well in an ide and dosn't cause me any cognative effort to understand. Its code you can use in any framework/cms and that is a huge bonus as we know so any developer would be able to work with your code.

You might find it simpler to concat the strings first then build the return object, especially if your iterating through a loop.

If you really need help identifying your variables, then maybe using {$tim -> getStuff($braces);}  like zend etc might be useful to you? This also highlights nicely in most ide.

Looking at html_writer::tag am i write in saying its built that way because it fits with the syntax used by pear quickforms in moodle (cough!and builds up yet another barrier to developers from outsiders of moodle while firmly placing moodle in the legacy code/over engineered camp it so often gets critisim for?

Be interested to see what stack overflow had to say about this...

Cheers

Stuart {boycoting the tinymce spellchecker}

 
Average of ratings: -
Tim at Lone Pine Koala Sanctuary
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

html_writer has nothing to do with quickforms.

And the concept is not terribly novel. Many languages have similar liabraries. 

(I found those in 5 mins with Google.)

Anyway, you don't like this coding style. I like it because I think it eliminates the potential for many bug. Niether of us is going to persuade the other, so let's stop this pintless discussion and get back to making Moodle better for users.

 
Average of ratings: -
Picture of Stuart Lamour
Re: All about html_writer
Group Particularly helpful Moodlers

shortcode systems such  http://lesscss.org/http://coffeescript.org/ and jquery are great because they enable you to write less code in a much more understandable way to generate consistant code.

for example in jquery i can write $('#myid).function(do stuff); instead of the long form standard js way.

less/sass ae similar with css variables and mixins that as with coffeescript removes all the unnecesary stuff allowing you as the programmer to concentrate on the important maintainable code logic.

shortform generally tends to be shorter to write, not longer. Its intention is to make code more understandable and easier to write, not less understandable and more difficult to write.

markdown is a good example of html shortform. html_writer:: is not.

hope this makes sense!

 

 
Average of ratings: -
Picture of Stuart Lamour
Re: All about html_writer
Group Particularly helpful Moodlers

P.s. Awaiting your php_writer.

Xx

A java guy

 
Average of ratings: -
Picture of David Scotson
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers

Do other similar systems in PHP really just write plain HTML into their code? How is it possible for them to maintain consistency? Is that all done manually within the theme?

Just had a look at Wordpress, they seem to have a mix of templates that call functions from a standard API and functions that embed the HTML directly. The functions are overridden by themes. Then they also have the shortcode system to embed functions into content.

One pattern I see is that they use quite "programmery" things to to generate the list of classnames, then just insert the result in as text e.g.:


$css_class = implode( ' ', apply_filters( 'page_css_class', $css_class, $page, $depth, $args, $current_page ) );

$output .= $indent . '<a href="'.get_permalink($page-">' . $link_before . apply_filters( 'the_title', $page->post_title, $page->ID ) . $link_after . '</a>';

from: https://github.com/WordPress/WordPress/blob/master/wp-includes/post-template.php

I'd kind of been working on the principle that if you could cope with imploding filtered arrays then you'd be happy enough calling a function to generate the HTML. But maybe people just ignore the scarier bits and change the stuff they recognise?

 
Average of ratings: -
Picture of David Scotson
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers
The general concept is good if your doing some complex code to decide what the id, class, and href etc. for a tag is going to be. Then you want functions that will e.g. let you add or remove a class and not have to worry about quoting and adding a space between each name etc. . That rapidly gets nasty if you're just writing the low level code.

But for outputting "<div>" it's clearly overkill. Well, I say "clearly" but do we expect everyone working on renderers to understand when and where this is a good idea and what the tradeoffs are?

 
Average of ratings: -
Picture of David Scotson
Re: All about html_writer
Group DevelopersGroup Documentation writersGroup Particularly helpful Moodlers
You joke about a css_writer but there's actually a bunch of solutions for CSS that do almost exactly that, e.g. the LESS compiler I use:

 
// LESS

.rounded-corners (@radius: 5px) {
  -webkit-border-radius: @radius;
  -moz-border-radius: @radius;
  -ms-border-radius: @radius;
  -o-border-radius: @radius;
  border-radius: @radius;
}

#header {
 .rounded-corners;
}
#footer {
 .rounded-corners(10px);
}



/* Compiled CSS */

#header {
 -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  -ms-border-radius: 5px;
  -o-border-radius: 5px;
  border-radius: 5px;
}
#footer {
 -webkit-border-radius: 10px;
  -moz-border-radius: 10px;
  -ms-border-radius: 10px;
  -o-border-radius: 10px;
  border-radius: 10px;
}
 


Writing CSS and HTML can be really tricky sometimes, but introducing systems to make it easier in some ways can make other things harder. It's a very messy problem space.
 
Average of ratings: -
Picture of Stuart Lamour
Re: All about html_writer
Group Particularly helpful Moodlers

css vars are also in chrome canary - been having lots of fun using them smile

 
Average of ratings: -