This is a document written using ReMarkable, a shorthand syntax for generating HTML.

{	"date"		:	201201041458,
	"updated"	:	201201070905,
	"licence"	:	"cc-by",
	"tags"		:	["web-dev", "code-is-art"],
	"enclosure"	:	["domtemplate.php"]
}

<section>

# Making the Ugly Elegant: Templating With DOM #

&__TOC__;

<aside>
	*Update:* Added <XML caveats (#caveats)> and updated code to v2: _
	HTML entities are automatically converted back to unicode.
</aside>

*Templating is quick to do in any particular way, but doing it _right_ is hard.* I can't count how many hip new template engines have popped up in just the last few years alone. I’m about to add one to the pile, but it is certainly not ‘hip’. It is however the closest I have ever gotten to the fabled golden fleece of "100% separation". Unlike most other forms of templating, this _really_ doesn't mix logic and HTML, nor does it try to mask the blatent logic ("if this, then this") by renaming 'logic' or using a `{{special syntax}}`.

What we're going to do is this: take a static (and I mean static) HTML page, load it into the {DOM} as an XML tree and then use the PHP as your logic, removing bits of the template not needed and changing the text about.

I got this idea from this blog post: <~Your templating engine sucks and everything you have ever written is spaghetti code (yes, you)~ (//www.workingsoftware.com.au/page/Your_templating_engine_sucks_and_everything_you_have_ever_written_is_spaghetti_code_yes_you)>. The article itself is long, agressive, rambling and fails to demonstrate the principle concretely. I simply ignored all the text and focused on the core principle that was being noted: instead of embedding some form of code in the HTML (even if it's just evolved search/replace syntax), just load the HTML into {DOM} and minpulate there so that the HTML itself is _ignorant_ of the templating.

By doing this, the HTML file itself can be designed independently of the software, and that whoever does the HTML doesn't 
have to know PHP. You could change the whole server language and it wouldn’t change the template one bit. More importantly you can actually view the whole look of the template in the browser without running the software. The reason I’m adopting this templating approach for <NoNonsense Forum (/nononsense_forum)> is to make it easier for anybody to modify the look of their forum without having to learn PHP, and hopefully encourage more contribution from all skill levels.

It took a few revisions, a few days and a lot of head-wracking to beat the {DOM} into something elegant, but here it is, NoNonsense Templating:


How It Works (#how)
============================================================================================================================
The first thing to wrap your head around is that {DOM} templating works on the principle of mostly taking away rather than adding. Logic-wise this is more difficult to get used to than you would think; you will be used to adding data according to logic rather than "if this, then remove the thing that it is not".

Firstly your template should be a static HTML page that contains all of the content and 'possibilities' of your output, where by we will remove what is not relevant to the page. For example:

~~~ HTML ~~~>
<p id="login" data-template="logged-out">
	You are not logged in.
</p>
<p id="login" data-template="logged-in">
	You are logged in as <b data-template="username">Bob</b>
</p>
<~~~

There are two things to note about this example:

:: “``<p id="login" />``” is listed twice, that’s not valid HTML!
	That's because these two paragraphs will be styled the same way by the CSS (``#login``), but the templating logic
	will remove which one is not relevant, leaving only one ``#login``.
	
:: What is “``data-template="logged-out"``”? Isn't that just a ‘special syntax’?!
	This is an HTML attribute (valid HTML5). I know it looks like ‘special syntax’, but it has nothing to do with logic.
	It is an anchor-point by which we can locate the element we want to modify—what element it is doesn’t matter to the
	logic. These HTML attributes are removed when the template is outputted.
	
	The reason why this is not a "special-syntax" is that we are _not mixing two different languages, syntaxes or programming models in one HTML file_. If you change your templating engine, it’s still HTML. If you change your logic,
	it's still HTML. Special syntaxes invent another language to intermix with HTML and thus add _programatic concepts_
	to a _declartive syntax_—which is not clean separation no matter what you name it.
	
	|	Ever since the ’Web was invented there has been a transluscent, yet intransient divisor between those
	|	developers who understand the fundamental <difference (/html_lasts)> between a declerative markup syntax
	|	and a programming language, and those who don’t. Some learn to see this difference, others simply ignore
	|	it and believe that it is a swell idea to tie structured data to a structured program that will bit rot one
	|	thousand times quicker than the data will. If you are trying to replace HTML or CSS with JavaScript,
	|	you are doing it wrong and have just signed a maintenance contract from hell, with yourself, for yours and
	|	your data’s life.
	|
	| <Kroc Camen—I Don’t Want to Do This Any More (/dont_wanna)>

In the PHP we can modify the HTML this way:

(((Please note that templates you load must be valid XML and have a single root node—e.g. “``<html>``”—in order to work, the examples in this article omit this for simplicity.)))

~~~ PHP ~~~>
//load the template and provide an interface
$template = new DOMTemplate ('test.html');

//lets imagine the user is logged in, remove the logged-out section and set the username
$template->remove ('logged-out');
$template->setValue ('username', 'Alice');
<~~~

The command “``remove ('logged-out')``” finds all elements that have a ``data-template`` attribute set to "`logged-out`" and deletes them.

The ``setValue`` method sets the text-content of an element, removing anything that was within. By replacing element content it means that you can provide dummy text to test the look and feel of your template, and it will be replaced with the real data. No more staring at ``{{NAME_GOES_HERE}}``!

Under the hood an XPath is constructed so that "`logged-out`" becomes "``.//*[@data-template="logged-out"]``". The shorthand syntax also supports specifying a required element type and/or an attribute to target, e.g:

~~~ PHP ~~~>
$template->setValue ('a:my-button@href', '/some_url');
<~~~

You can also use full <XPath syntax (//www.w3schools.com/xpath/xpath_syntax.asp)> in any of the functions that accept a shorthand query by prefixing the string with "`xpath:"`:

~~~ PHP ~~~>
//if using HTTPS, change the Google search box to use HTTPS too
if (@$_SERVER['HTTPS'] == 'on') $template->setValue (
	'xpath://form[@action="http://google.com/search"]/@action', 'https://encrypted.google.com/search'
);
<~~~

Looping is always a sore point in templating. How do you take a chunk and repeat it down the page without having to define a ton of logic in your templates?

Looping with the {DOM} is shockingly elegant!

~~~ PHP ~~~>
$item = $template->repeat ('list-item');
foreach ($data as $value) {
	$item->setValue ('item-name', $value);
	$item->next ();
}
<~~~

The ``repeat`` method takes an element (via shorthand/XPath) to be used as the repeating template and removes it from the document, then you just set and remove elements from the repeating template as if it were its own template. Once you've templated that iteration you call the ``next`` method and the HTML is added to the bottom of the parent node, then the template repeater resets itself back to the original HTML so you can template it again!

Once you've made all your changes to the template, just retrieve the final HTML and output.
The ``data-template`` attributes will be removed from the output automatically.

~~~ PHP ~~~>
die ($template->html ());
<~~~

See <the API (#api)> for details of all the functions.


The Code (#code)
============================================================================================================================
•	<View source code (/&__HREF__;/domtemplate.php)>
•	<View source code on GitHub, where you can contribute changes and comment on the code (https://gist.github.com/1560375)>
•	<Discuss this article in the forum (//forum.camendesign.com)> (no registration required)

If you would like to see a real-world use of this templating system with a ton of examples you can draw from real, practical code you can examine the source code of my forum system called <NoNonsene Forum (/nononsense_forum)> here:

(((At the moment the use of {DOM} templating is in an experimental branch of the software, so these URLs will change when it is merged back into the main-line)))

•	<The PHP logic (https://github.com/Kroc/NoNonsenseForum/blob/themes3/index.php)>
•	<The HTML template (https://github.com/Kroc/NoNonsenseForum/blob/themes3/themes/greyscale/index.html)>


Caveats (#caveats)
============================================================================================================================
:: Whitespace handling is good, but not perfect
	In the case of repeating an element the whitespace within is kept, but the whitespace outside the element is not.
	This is not a major problem, it just means that the closing and opening tags of your lists will be paired
	(e.g. “``…</li><li>…``”).
	
	The biggest issue is that when elements are removed, the whitespace around them remains, meaning that you get a
	number of blank lines in the output HTML where the elements used to be. There's no direct way of handling this
	other than perhaps using a search/replace to remove blank lines in the HTML after it's been templated.
	
	One benefit of using the {DOM} however is that if you want minify the HTML a little, you can just add
	“``$this->DOMDocument->preserveWhiteSpace = false;``” to the second line of the constructor function of
	``DOMTemplate`` and the ``html`` method will return the markup as a big blob with few line-breaks.
	
	If you add “``$this->DOMDocument->formatOutput = true;``” instead, the markup will be ‘tidied’ for you,
	re-nesting the elements neatly in an easy to read fashion.

:: XML woes
	The biggest problems are all related to XML not being HTML. Until such a time PHP’s {DOM} is updated to treat HTML
	like it isn’t 1999 anymore, you’ll need to work with the following knowledge:
	
	•	HTML that is not valid XML will return blank instead. Ensure your templates are valid XML and check your
		server's error log to see parsing errors. If you are templating HTML content generated by users, be aware
		that any invalid HTML will be ignored entirely!
	
	•	HTML entities like “`&copy;`” are not understood in XML and will cause the whole template not to load.
		With v2 of the DOMTemplate class many (248) of these will be converted back to unicode automatically to
		avoid errors, but it is not
		<comprehensive (//whatwg.org/specs/web-apps/current-work/multipage/named-character-references.html)>
		(~2100).
	
	•	HTML that you load either through <``DOMTemplate`` (#api-new)> or apply using <``setHTML`` (#api-sethtml)>
		_must_ have only one root node. E.g. A list of elements can not be used unless wrapped by an element.
	
	•	Scripts must be commented to hide the text from the XML parser: {i.e|that is}:
		
		~~~ XML ~~~>
		<script><!--
			⋮
		--></script>
		<~~~

The API (#api)
============================================================================================================================
Instantiation: ``new DOMTemplate (string $filepath)`` (#api-new)
----------------------------------------------------------------------------------------------------------------------------
Specify the file path to the template file to load when instantiating the template class.
It must be valid XML (but not specifically XHTML! The XMLNS breaks it), and have one root element.

~~~ PHP ~~~>
$template = new DOMTemplate ('index.html');
<~~~

Shorthand XPath Syntax: (#api-shorthand)
----------------------------------------------------------------------------------------------------------------------------
All of the methods that accept a query (<``repeat`` (#api-repeat)>, <``setValue`` (#api-setvalue)>, <``set`` (#api-set)>, <``setHTML`` (#api-setHTML)>, <``addClass`` (#api-repeat)> & <``remove`` (#api-repeat)>) use a shorthand-syntax where you only need to provide the name of the ``data-template`` attribute you want to target and the full <XPath query (//www.w3schools.com/xpath/xpath_syntax.asp)> is built for you.

E.g. “`my-button`” would be automatically be translated to “`.//*[@data-template="my-button"]`”.

An optional element type can be provided:

“`a:my-button`” becomes “`.//a[@data-template="my-button"]`”.

An optional attribute name can be provided which will be the target of the <``setValue`` (#api-setvalue)>, <``set`` (#api-set)> and <``remove`` (#api-repeat)> methods:

“`a:my-button@href`” becomes “`.//a[@data-template="my-button"]/@href`”

To use your own XPath query, just prepend the query with "`xpath:`", _
e.g. “`xpath:/html/head/title`”.

Method: ``html ()`` (#api-html)
----------------------------------------------------------------------------------------------------------------------------
Returns a string of the HTML of the template. Any remaining ``data-template`` attributes will have been removed.

~~~ PHP ~~~>
die ($template->html ());
<~~~

Method: ``repeat (string $query)`` (#api-repeat)
----------------------------------------------------------------------------------------------------------------------------
Takes a <shorthand XPath query (#api-shorthand)> and returns a ``DOMTemplateRepeater`` object instantiated with the first element returned by the query. The ``DOMTemplateRepeater`` object supports all of the proceeding methods, as well as sub-repeating.

#### Method: ``next ()`` #### (#api-next)
Takes the current HTML content of the ``DOMTemplateRepeater`` object and appends it as a last-child of the original parent element (that is, adds it to the bottom of the list), then resets its HTML content back to the original HTML it had when it was created.

~~~ PHP ~~~>
$item = $template->repeat ('list-item');
foreach ($data as $value) {
	$item->setValue ('item-name', $value);
	$item->next ();
}
<~~~

Method: ``setValue (string $query, string $value)`` (#api-setvalue)
----------------------------------------------------------------------------------------------------------------------------
Replaces the content of all elements matched with the <shorthand XPath query (#api-shorthand)> with the given value. The string value is HTML-encoded, so any HTML in the value will appear as-is, rather than be rendered as HTML. Use the <``setHTML`` (#api-sethtml)> method to insert actual HTML content.

~~~ PHP ~~~>
$template->setValue ('name', 'Kroc');
<~~~

Method: ``set (array $queries)`` (#api-set)
----------------------------------------------------------------------------------------------------------------------------
Allows you to write code in a more compact way by specifiying an array of <shorthand XPath queries (#api-shorthand)> and their associated value to set.

~~~ PHP ~~~>
$template->set (array (
	'name'	=> 'Kroc',
	'site'	=> 'http://camendesign.com'
));
<~~~

Method: ``setHTML (string $query, string $html)`` (#api-sethtml)
----------------------------------------------------------------------------------------------------------------------------
Replaces the content of all elements matched with the <shorthand XPath query (#api-shorthand)> with the given HTML. Unlike <``setValue`` (#api-setvalue)> the HTML is not encoded, and so will be rendered by the browser. Please ensure that you have already safely encoded the text portions of the HTML you pass if they contain user-provided text.

~~~ PHP ~~~>
$template->setHTML ('about', '<p>The quick brown fox jumps over the lazy dog</p>');
<~~~

Method: ``addClass (string $class)`` (#api-addclass)
----------------------------------------------------------------------------------------------------------------------------
Adds the specificed HTML class name to every element matched with the <shorthand XPath query (#api-shorthand)>. If an element already has a class attribute, mutliple class names will be separated by spaces when the new class is added.

~~~ PHP ~~~>
$template->addClass ('section', 'open');
<~~~

Method: ``remove (string $query)`` (#api-remove)
----------------------------------------------------------------------------------------------------------------------------
Deletes all the elements (and their children) matched with the <shorthand XPath query (#api-shorthand)>.

~~~ PHP ~~~>
$template->remove ('secret-stuff');
<~~~


</section>