Requests for comment/Fluent interface for Html building

From mediawiki.org
Request for comment (RFC)
Fluent interface for Html building
Component General
Creation date
Author(s) Bawolff
Document status in draft

These are some rough thoughts I had, which I wanted to write down. I'm not entirely sure about this yet, and its not ready to be an RFC yet. But I thought it'd be useful to write it down.


Currently we primarily use the Html class to generate html in mediawiki. This class is somewhat awkward to use when generating complex html structures. There has been some movement towards templates, but they don't seem to have overly caught on in core.

Problems with current Html class:

  • Easy to loose track of where stuff is escaped. Is the enclosing content an Html::element, or Html::rawElement. Often code is htmlspecialchars several lines before wrapping in an Html::rawElement (violates rule of escape at the very end). With messages things get harder - since often you do $bar = wfMessage( 'foo' )->parse() quite a bit before you output. Or maybe you do wfMessage( 'foo' )->escaped() in a Html::rawElement. Or perhaps you do wfMessage( 'foo' )->text() in an Html::element. Its all very confusing and easy to loose track
  • Generating html by doing things like $part1 = Html::element( 'div', ... ); $part2 = Html::rawElement( 'div', [], $part1 ); all gets rather hard to keep straight, or you end up with 17 billion levels of indentation.

However, in javascript, we often use things like $( '

' ).append( ... ) and somehow it all seems less confusing.

Similarly, lua has HtmlBuilder, which seems quite popular.

So lets build a wrapper around the Html class for MW that's more in the style of jQuery or lua HtmlBuilder (I guess this style is called "fluent").

Design goals:

  • Make it very clear when raw html is being included
  • Avoid any pattern where you concat or include raw html, unless you are actually including arbitrary html (So avoid $foo = wfMessage( 'bar' )->parse(); Html::rawElement( 'div', [], $foo );)
  • Use the method chaining style, like wfMessage() and jQuery's $()


So what I envision is have a new class: HtmlBuilder

$html = new HtmlBuilder( $whateverDependenciesThisHas ); // Or MediaWikiServiceLocator - whatever that is.
$html->tag( 'div' )
  ->attr( 'id', 'foo' )
  ->appendText( "Some textual content that get's <html> escaped." )
  ->appendMsgParsed( "msgname", "arg1", "arg2" )
  ->appendRawHtml( "If for some reason you <b>really</b> need it, you can still add arbitrary html." )
  ->prependTag( 'hr' );

This would make something like:

<div id="foo">
 <hr>
 Some textual content that get's &lt;html&gt; escaped. [Parsed content of msgname here] If for some reason you <b>really</b> need it, you can still add arbitrary html.
</div>

Actual API[edit]

So far, I'm envisioning two classes: HtmlBuilder and HtmlBuilderTag

Roughly speaking I'm imaginging something like:

HtmlBuilder[edit]

HtmlBuilder::tag( $tagName )
Returns an HtmlBuilderTag with the starting content being <$tagName></$tagName>
HtmlBuilder::text( $text )
HtmlBuilderTag that's just (escaped) text. Maybe equivalent to HtmlBuilder->tag( '' )->appendText( $text );
HtmlBuilder::rawHtml( $text )
same as ->text() but for raw Html.

HtmlBuilderTag[edit]

HtmlBuilderTag::attr( $attr, $value )
Adjust attributes of the current context tag. Returns HtmlBuilderTag
HtmlBuilderTag::appendText( $text )
Escape and append some text
HtmlBuilderTag::append( $arg )
Append argument. Do something different depending on what the argument is. If its a string, same as appendText(). If its an instance of Message, same as appendMsgParsed (Or maybe it should default to ->escaped()??), if its an instance of HtmlBuilderTag, append that as a subtree
HtmlBuilderTag::appendRawHtml( $html )
Strongly discouraged, but allow user to append arbitrary html
HtmlBuilderTag::renderAsString()
Output html as string

[And probably more]

Also similar methods for prepend*, insertBefore*, insertAfter*

Example[edit]

Todo: Should rewrite something using Html::[raw]Element to see what it would look like using this proposal.

Other questions[edit]

  • Maybe someone has already implemented something like this for PHP which we could just steal?
  • oojs is appearently kind of similar to this, and already exists.