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.
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>
<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!
<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
| 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.
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 )
$tpl = new Tpl("tpl/outer.tpl");
echo $tpl->parse();
$tpl->setAttrib("TITLE","Hello World");
$tpl->setAttrib("BODY",$someOtherTpl);$tpl->setAttribs( $dbRow ); $tpl->setAttribs( array( 'DEV'=>'bibby', 'URL'=>'http://bbby.org' ));
$dev = $tpl->getAttrib('DEV');$tpl->clearAttribs();
$tpl->setClear( TRUE );
$tpl->loop( $users ); $tpl->loop( array ( array ( 'NAME'=>'bbby', 'URL'=>'http://bbby.org' ), array ( 'NAME'=>'chocobo', 'URL'=>'http://www.chocobo.com/' ) ) );
$tpl = Tpl::string( "{LAST_NAME}, {FIRST_NAME} — {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.
<?/**
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.
<table border="1">
<caption>{TITLE}</caption>
<tr>
<th>{ITEM_DESCRIPT}</th>
<th>{DATA_DESCRIPT}</th>
</tr>
{ROWS}
</table>
<tr>
<td>{CAUSE}<sup>{CITATION}</sup></td>
<td>{LOW_EST}-{HI_EST}</td>
</tr>
<h4>{TITLE}: {ITEM_DESCRIPT} ({DATA_DESCRIPT})</h4>
<ul>
{ROWS}
</ul>
<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.
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: