Briefly, about this page

This page describes the purpose and usage of one of my classes, Tpl.class.php, which makes use of html templates easy and painless. I created this page primarily for my buddy, ChrisG, who does some HTML and might benefit from using templates. The design of this page can be attributed to Jeremy Duenas, and was downloaded free from Open Designs.

What is a template?

If you've seen the unrendered source of a web page (Ctrl+U in Firefox), then you'll see that (ignoring javascript for now) the final markup that the browser reads from the server is what makes the page. How that markup is generated could vary a lot from one environment to another, like one that uses PHP to compile a report from a database for example. It could be true, as is the case of this very page, that the markup is not dynamically created at all — it's just a flat page. But in most web applications and highly dynamic sites, templates may be employed to make a developer's work easier, if not automatic.

For contrast, here's a simple webpage as a single chunk of HTML

<html>
	<head>
		<title>A very tiny web page</title>
		<link rel="stylesheet" type="text/css" media="screen" href="foo.css">
	</head>
	<body>
		<h2>Hello world.</h2>
		<p>
			Welcome to my itty bitty webpage                                                                                                                                                                      
		</p>
	</body>
</html>
	

What is considered a template is a piece of that HTML broken off and set aside in it's own file. Its content-specific information is removed and replaced with a tag that some other code will fill in. For example, we could make our <head> into a template:

head.tpl
	<head>
		<title>{PAGE_TITLE}</title>
		{STYLE_SHEET}
	</head>
	

It is especially helpful that templates be reusable. In the instance above, any page that I make could use head.tpl with any title and stylesheet. Taking a closer look at the page you are reading now, there is something of a pattern happening here. I'm offering text to be read as a cluster of paragraphs with a header — that could be a template!

text_block.tpl
	<h2>{BLOCK_TITLE}</h2>
	{BLOCK_TEXT}
	

A common goal among experienced web developers is to develop and deploy applications that are flexible and easily maintainable. An important consideration in reaching this goal is the separation of business logic from presentation logic. Developers use web template systems (with varying degrees of success) to maintain this separation.

source: wikipedia.org

Business and Presentation

Consider this table of data (source: wikipedia.org):
Oregon-California-Mormon Trail Deaths
Cause Estimated deaths
Cholera1 6,000-12,500
Indian attacks2 500-1,000
Freezing3 300-500
Run overs4 200-500
Drownings5 200-500
Shootings6 200-500
Miscellaneous7 200-500
Scurvy8 300-500

There are two distinct ways to think about what we see:

The backend data-crunching element that fetches the data shouldn't care how it is displayed to the end user. Likewise, the HTML template shouldn't care what manner of data is given to fill its tags, just how it looks. With this in mind, it's a short leap to displaying the same data as a list instead of as a table.

Oregon-California-Mormon Trail Deaths: Cause (Estimated deaths)

The Tpl class

What I want to do is demonstrate how the Oregon Trail dataset can be templated to be a table and a list, but first we should learn how to use the Tpl class. For sake of simplicity, I'll document only the public methods here.

Tpl( $filename )

Class constructor. Accepts a path/to/file to a template to use.
$tpl = new Tpl("tpl/outer.tpl");
parse()
This method finishes the template and returns its contents.
echo $tpl->parse();
setAttrib($tag,$value)
Sets the $tag found in the template to $value. $value can be a string or another instance of Tpl.
$tpl->setAttrib("TITLE","Hello World");
$tpl->setAttrib("BODY",$someOtherTpl);
setAttribs( $array )
Accepts an array of items, and setAttrib is called on each.
$tpl->setAttribs( $dbRow );
$tpl->setAttribs( array(
	'DEV'=>'bibby',
	'URL'=>'http://bbby.org'
));
getAttrib( $tag )
Returns a value set to a tag, in case you forgot what it was.
$dev = $tpl->getAttrib('DEV');
clearAttribs()
Makes the tpl forget everything that was set.
$tpl->clearAttribs();
setClear( $bool )
This enables "tag clearing", meaning that unset tags in the template won't show.
$tpl->setClear( TRUE );
loop( $dataset )
Repeats the template over a set of data. Where setAttribs takes an array of data, a dataset here is an array of those.
$tpl->loop( $users );
$tpl->loop( 
	array
	(
		array
		(
			'NAME'=>'bbby',
			'URL'=>'http://bbby.org'
		),
		array
		(
			'NAME'=>'chocobo',
			'URL'=>'http://www.chocobo.com/'
		)
	)
);
	
string( $tpl_string )
Sometimes, it's not always convenient to create a new template file for every desired template. This method, which can be used statically, can create a new template from a string; making quick views.
$tpl = Tpl::string( "{LAST_NAME}, {FIRST_NAME} &mdash; {PHONE}<br />\n" );
$tpl->loop( $users );
	

That should get us started with the business end of things. I'll go ahead and write this to switch on the templates we're using to show how easy it is to change the presentation.

business.php
<?/**
Tpl class business end
*/

require('cls/Tpl.class.php');
require(
'cls/fake_DB.class.php'); // imaginary

// set up our templates
$useList FALSE// set to TRUE if you want a list
if($useList)
{
    
$outer "tpl/list.tpl";
    
$item "tpl/item.tpl";
}
else
{
    
$outer "tpl/table.tpl";
    
$item "tpl/row.tpl";
}

$outerTpl = new Tpl($outer);
$itemTpl = new Tpl($item);

// get our data, presumably from a DB
$db = new fake_DB($someConnectConfig);
$q="SELECT cause, citation, low_est, hi_est from OregonTrailDeaths 
ORDER BY hi_est desc, cause asc"
;
if(!
$db->query($q))
    die( 
$db->err );

// create a dataset
$dataset = array();
while(
$r $db->fetchRow() )
    
$dataset[] = $r;
    

// apply the data to the row tpl
$itemTpl->loop$dataset );

// set rows to the outer
$outerTpl->setAttrib('ROWS'$itemTpl);

// set some other vars
$outerTpl->setAttribs(array(
    
'TITLE'=>'Oregon-California-Mormon Trail Deaths',
    
'ITEM_DESCRIPT'=>'Cause',
    
'DATA_DESCRIPT'=>'Estimated deaths'
));

// print and done
echo $outerTpl->parse();

?>

It's a bad example that my code decides which set of templates to use (that may be some other class's job). After all, we're trying to separate logic, but for this demo I think it's ok. The data is the same, and once the templates are chosen, plugging the values in is the same. Now, say our query generated associative arrays using the field names we asked for: our dataset may very much look like this:

array(
	array(
		'CAUSE'=>'Cholera'
		'CITATION'=>1,
		'LOW_EST'=>'6,000',
		'HI_EST'=>'12,500'
	),
	array(
		'CAUSE'=>'Indian attacks'
		'CITATION'=>2,
		'LOW_EST'=>'500',
		'HI_EST'=>'1,000'
	),
	  ...
)

Calling loop($dataset) runs each item through setAttribs and applies it to the template. So what we need are two templates for each of our desired outputs: one for an item, and one to wrap them in a table or list.

templates

Table View: table.tpl

<table border="1">
	<caption>{TITLE}</caption>
	<tr>
		<th>{ITEM_DESCRIPT}</th>
		<th>{DATA_DESCRIPT}</th>
	</tr>
	{ROWS}
</table>

Table View: row.tpl

<tr>
	<td>{CAUSE}<sup>{CITATION}</sup></td>
	<td>{LOW_EST}-{HI_EST}</td>
</tr>

List View: list.tpl

<h4>{TITLE}: {ITEM_DESCRIPT} ({DATA_DESCRIPT})</h4>
<ul>
	{ROWS}
</ul>

List View: item.tpl

<li>{CAUSE}<sup>{CITATION}</sup> ({LOW_EST}-{HI_EST})</li>

That's it, pretty simple! That "tags" I'm using in my templates are in all capital letters, but you don't necessarily have to do that, as a tag is defined by a word between curly brackets. Using the item tpl's loop method, the single tpl self replicates and populates to the size of the dataset, so it doesn't matter how many rows are involved.

Source

Here, is the source to the Tpl class and some working examples of what I've described as a table or as a list using these templates: