Reverse Callback Templating
by James RobsonMarch 14, 2008
Programmers have long recognized that separating code logic from presentation is good. The Perl community has produced many fine systems for doing just this. While there are many systems, they largely fall within two execution models, pipeline and callback (as noted by Perrin Harkins in Choosing a Templating System). HTML::Template and Template Toolkit are in the pipeline category. Their templates consist of simple presentation logic in the form of loops and conditionals and template variables. The Perl program does its work, then loads and renders the appropriate template, as if data were flowing through a pipeline. Mason and Embperl fall into the callback category. They mix code in with the template markup, and the template "calls back" to Perl when it encounters program logic.
A third execution model exists: the reverse callback model. Template and code files are separate, just like in the pipeline approach. Instead of using a mini-language to handle display logic, however, the template consists of named sections. Perl executes and calls a specific section of the template at the appropriate time, rendering it. Effectively, this is the opposite of the callback method, which wraps Perl logic around portions (or sections) of a template in a single file. Reverse callback uses Perl statements to load, or call, specific portions of the the template. This approach has a few distinct advantages.
A Reverse Callback Example
Suppose that you have a simple data structure you are dying to output as pretty HTML.
my @goods = (
"oxfords,Brown leather,\$85,0",
"hiking,All sizes,\$55,7",
"tennis shoes,Women's sizes,\$35,15",
"flip flops,Colors of the rainbow,\$7,90"
);
First, you need an HTML template with the appropriate sections defined. Sections are of vital importance; they enable Template::Recall to keep the logic squarely in the code. Template::Recall uses the default pattern /[\s*=+\s*\w+\s*=+\s*]/ (to match, for example, [==== section_name ====]) to determine sections in a single file. The start of one section denotes the end of another. This is because Template::Recall uses a split() operation based on the above regex, saving the \w+ as the section key in an internal data structure.
[ =================== header ===================]
<html>
<head>
<title>my site - [' title ']</title>
</head>
<body>
<h4>The date is [' date ']</h4>
<table border="1">
<tr>
<th>Shoe</th>
<th>Details</th>
<th>Price</th>
<th>Quantity</th>
</tr>
[ =================== product_row =================== ]
<tr>
<td>[' shoe ']</td>
<td>[' details ']</td>
<td>[' price ']</td>
<td>[' quantity ']</td>
</tr>
[= footer =]
</table>
</body>
</html>
This template is quite simple. It has three sections, a "header," "product_row," and "footer." The sections essentially give away how the program logic is going to work. A driver program would call header and footer only once during program execution (start and end, respectively). product_row will be called multiple times during iteration over an array.
Names contained within the delimeters [' and '] are template variables for replacement during rendering. For example, [' date '] will be replaced by the current date when the program executes.
The driver code must first instantiate a new Template::Recall object, $tr, and pass it the path of the template, which I've saved as the file template1.html.
use Template::Recall;
my $tr = Template::Recall->new( template_path => 'template1.html');
With $tr created, the template sections are loaded and ready for use. The obvious first step is to render the header section with the render() method. render() takes the name of the section to process, and optionally, a hash of names and values to replace in that section. There are two template variables in the header section, [' title '] and [' date '], so the call looks like:
print $tr->render( 'header', { title => 'MyStore', date => scalar(localtime) } );
The names used in the hash must match the names of the template variables in the section you intend to render. For example, date => scalar(localtime) means that [' date '] in the header section will be dynamically replaced by the value produced by scalar(localtime).
You probably noticed from the template that the header section created the start of an HTML table. This is a fine time to render @goods as the table's rows.
for my $good (@goods)
{
my @attr = split(/,/, $good);
my $quantity = $attr[3] eq '0' ? 'Out of stock' : $attr[3];
my %row = (
shoe => $attr[0],
details => $attr[1],
price => $attr[2],
quantity => $quantity,
);
print $tr->render('product_row', \%row);
}
In actual code, this array would likely come from a database. For each row, the driver makes necessary logical decisions (such as displaying "Out of stock" if the quantity equals "0"), then calls $tr->render() to replace the placeholders in the template section with the values from %row.
Finally, the driver renders the footer of the HTML output. There are no template variables to replace, so there's no need to pass in a hash.
print $tr->render('footer');
The result is this nice little output of footwear inventory:
The date is Fri Aug 10 14:22:30 2007
| Shoe | Details | Price | Quantity |
|---|---|---|---|
| oxfords | Brown leather | $85 | Out of stock |
| hiking | All sizes | $55 | 7 |
| tennis shoes | Women's sizes | $35 | 15 |
| flip flops | Colors of the rainbow | $7 | 90 |
The Logic Is in the Code
What happens if you extend your shoe data slightly, to add categories? For instance, what if @goods looks like:
my @goods = (
"dress,oxfords,Brown leather,\$85,0",
"sports,hiking,All sizes,\$55,7",
"sports,tennis shoes,Women's sizes,\$35,15",
"recreation,flip flops,Colors of the rainbow,\$7,90"
);
The output now needs grouping, which implies the use of nested loops. One loop can output the category header -- sports, dress, or recreation shoes -- and another will output the details of each shoe in that category.
To handle this in HTML::Template, you would generally build a nested data structure of anonymous arrays and hashes, and then process it against nested <TMPL_LOOP> directives in the template. Template::Recall logic remains in the code, you would build a nested loop structure in Perl that calls the appropriate sections. You can also use a hash to render the category sections as keys and detail sections as values in a single pass, and output them together using join.
The template needs some modification:
[====== table_start ====]
<table border="1">
[====== category =======]
<tr><td colspan="4"><b>['category']</b></td></tr>
[====== detail ======]
<tr><td>['shoe']</td><td>['detail']</td><td>['price']</td><td>['quantity']</td></tr>
[======= table_end ====]
</table>
This template now has a section called "category," a single table row that spans all columns. The "detail" section is pretty much the same as in the previous.
my %inventory;
for my $good (@goods) {
my @attr = split(/,/, $good);
my $q = $attr[4] == 0 ? 'Out of stock' : $attr[4];
$inventory{ $tr->render('category', { category => $attr[0] } ) } .=
$tr->render('detail',
{
shoe => $attr[1],
detail => $attr[2],
price => $attr[3],
quantity => $q,
} );
}
print $tr->render('table_start') .
join('', %inventory) .
$tr->render('table_end');
This loop looks surprisingly similar to the first example, doesn't it? That's because it is. Instead of printing each row, however, this code renders the first column in @goods against the category template section, and then storing the output as a key in %inventory. In the same iteration, it renders the remaining columns against the detail section and appends to the value of that key.
After storing the rendered sections in this way to %inventory, the code prints everything with a single statement, using join to print all the values in %inventory, including keys. The output is:
| recreation | |||
| flip flops | Colors of the rainbow | $7 | 90 |
| sports | |||
| hiking | All sizes | $55 | 7 |
| tennis shoes | Women's sizes | $35 | 15 |
| dress | |||
| oxfords | Brown leather | $85 | Out of stock |
The code also handles conditional output. Suppose that at your growing online shoe emporium you provide special deals to customers who have bought over a certain dollar amount. As they browse your shoe inventory, these deals appear.
if ( $customer->is_elite ) {
print $tr->render('special_deals', get_deals('elite') );
}
else {
print $tr->render('standard_deals', get_deals() );
}
What about producing XML output? This usually requires a separate template? You can conditionally load a .xml or .html template:
my $tr;
if ( $q->param('fmt') eq 'xml' ) {
$tr = Template::Recall->new( template_path => 'inventory.xml' );
}
else {
$tr = Template::Recall->new( template_path => 'inventory.html' );
}
Perl provides everything you need to handle model, controller, and view logic. Template::Recall capitalizes on this and helps to make projects code driven.
Template Model Comparison
It's important to note a few things that occurred in these examples -- or failed to occur, rather. First, there's no mixture of code and template markup. All template access occurs through the method call $tr->render(). This is strong separation of concerns (SOC), just like the pipeline model, and unlike the callback model, which mixes template markup and code in the same file. Not only does strong SOC provide good code organization, it also keeps designers from having to sift through code to change markup. Consider using Mason to output the rows of @goods.
% for my $good (@goods) {
% my @attr = split(/,/, $good);
% my $quantity = $attr[3] eq '0' ? 'Out of stock' : $attr[3];
<tr>
<td><% $attr[0] %></td>
<td><% $attr[1] %></td>
<td><% $attr[2] %></td>
<td><% $quantity %></td>
</tr>
% }
This is an efficient approach, and easy enough for a programmer to walk through. It becomes difficult to maintain though, when designers are involved, if for no other reason than because a designer and a programmer need to access the same file to do their respective work. Design changes and code changes will not always share the same schedule because they belong to different domains. It also means that in order to switch templates, say to output XML or text (or both), you have to add more and more conditionals and templates to the code, making it increasingly difficult to read.
The other thing that did not occur in this example is the leaking of any kind of logic (presentation or otherwise) into the template. Consider that HTML::Template would have to insert the <TMPL_LOOP> statement in the template in order to output the rows of @goods.
<TMPL_LOOP NAME="PRODUCT">
<tr>
<td><TMPL_VAR NAME=SHOE></td>
<td><TMPL_VAR NAME=DETAILS></td>
<td><TMPL_VAR NAME=PRICE></td>
<td><TMPL_VAR NAME=QUANTITY></td>
</tr>
</TMPL_LOOP>
That's not a big deal, really. If you care about line count, this only requires one extra line over the Template::Recall version, and that's the the closing tag </TMPL_LOOP>. Nonetheless, the template now states some of the logic for the application. Sure, it's only presentation logic, but it's logic nonetheless. HTML::Template also provides <TMPL_IF> for displaying items conditionally, and <TMPL_INCLUDE> for including other templates. Again, this is logic contained in the template files.
Template::Recall keeps as much logic as possible in the code. If you need to display something conditionally, use Perl's if statement. If you need to include other templates, load them using a Template::Recall object. Whereas the pipeline models likely work better for projects with a fairly sophisticated design team, Template::Recall tries to be the programmer's friend and let him or her steer from the most comfortable place, the code.
There is also a subtle cost to using the pipeline model for a simple loop like that above. Consider this HTML::Template footwear data code:
my $template = HTML::Template->new(filename => template1.tmpl');
my @output;
for my $good (@goods)
{
my @attr = split(/,/, $_);
my %row = (
SHOE => $attr[0],
DETAILS => $attr[1],
PRICE => $attr[2],
QUANTITY => $attr[3],
);
push( @output, \%row );
}
$template->param(PRODUCT => \@output);
print $template->output();
The code iterates over @goods and builds a second array, @output, with the rows as hash references. Then the template iterates over @output within <TMPL_LOOP>. That's walking over the same data twice. Template sections do not suffer this cost, because you can output the data immediately, as you get it:
print $tr->render('product_row', \%row);
This is essentially what happens with Mason (or JSP/PHP/ASP for that matter). The main difference is that Template::Recall renders the section through a method call rather than mixing code and template.
Template::Recall, by using sectioned templates, combines the efficiency of the callback model with the strong, clean separation of concerns inherent in the pipeline model, and perhaps gets the best of both worlds.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 10 of 10.
- New Template::Recall revision available
2008-03-27 07:02:33 James Robosn [Reply]
I just wanted to note that I have added some features to the module in response to feedback and experience in the field.
First, I've added a trim() function that allows you to control section whitespace prior to output.
Second, I've found in using Template::Recall that it's not always convenient to output data immediately when you call render(). I've modified it so you may store the rendered sections internally, and then put them together later, in any order, using the assemble() method.
The updated version and details are on CPAN:
http://search.cpan.org/~gilad/Template-Recall-0.14/lib/Template/Recall.pm- Best People Search - People Finder, Public Records, Background Checks
2009-04-01 02:09:32 vcaobest [Reply]
vcao.net( http://www.vcao.net )This is a nationwide provider of background checks resources, employment screening, criminal records and public records services to individual, businesses, corporations and consumers across the United States. We provide individuals and businesses with access to several instant search types, including; criminal records, driving records, social security address history, credit reports, employment records and references, many more.
Our Main Services:
1,Criminal Records
2,Court Records
3,Medical Records
4,School Records
5,People Search
We are a private security firm who trace missing children and persons, internationally. Often, we have nothing but a name and an approximate age, with the last place the subject was seen. Your people search products help us average one person found per day. US Search's background check service is very efficient. Thank you for an excellent background check database.
Our of course took advantage of the great offer for the background check service, and want to thank US Search for it. My entire experience, from my first people search inquiry, has been a pleasure, and in these impersonal times that's saying a lot! I mention the personal element because that is always important to me and the people on your 800 line have all passed the test, the one I apply which is pretty critical. I'll give you 5 stars for your people search service.
we would like to take the time to thank you guys for an excellent job on the background check. I've been looking for this individual for the last past 4 years. And with your background check results, I now have the chance to save my child's life. Thanks again.
Our Database helps you find the information you need to make sound life decisions - for you, your family, and your home and business. We are proud to be the leading provider of people search and background checks.
This is a nationwide provider of background checks resources, employment screening, criminal records and public records services to individual, businesses, corporations and consumers across the United States, Asia, Europe, Australia, Africa, and countries like Mexico, New Zealand.
We provide individuals/businesses with access to several instant search types, including; criminal records, sex offender records, driving records, social security address history, credit reports, employment records and references, many more.
For more information and free downloads visit VCAO at http://www.vcao.net.
- Best People Search - People Finder, Public Records, Background Checks
- Speed?
2008-03-23 13:56:21 sigzero [Reply]
I would be interested to know if one way of the other was actually faster.- Speed?
2008-03-26 07:14:02 James Robosn [Reply]
The only place where I can say that Template::Recall has a noted efficiency advantage over pipeline systems is with simple loops (as described in the article) because of the two iteration cost. Beyond that, it would take some benchmark testing to determine differences, and even then, some costs would be negligible in various scenarios. My own perception is that all of the available tried-and-true template systems will be fast enough for most situations.
As for Template::Recall, it's fast in that its basic operation is simple, essentially substitution.
- Speed?
- typo3 does have something like that too
2008-03-19 13:08:54 rico.moorman [Reply]
The PHP CMS/framework Typo3 does have a similar approach to "reverse callback templating".
Within the typical flow of the "standard template building" the designer just drops his/her HTML files somewhere. The programmer usually just has to add some "special" HTML comments to and some "TypoScript" glue to put the right stuff on the right places and to repeat stuff where it has to be repeated.
Quite separated indeed.
And of course there are also ways to use this functionality without typoscript too.
http://typo3.org/documentation/document-library/tutorials/doc_tut_templselect/0.1.0/view/1/3/
One thing to consider though. In order to make stuff cleaner one would probably want to extract the template-dependent presentation logic (repeating for the records and conditionals) within a separate intermediate layer.
Within typo3 I usually would have my main plugin tie the models together and forward their gathered information to some special methods/classes.
In turn these work on a certain template file which is then taken apart and put together according to the programmers intentions.
One could indeed call these special classes and methods in combination with the templates "views" because they are just doing that.
The reverse callback templating just adds a sometimes convenient extra layer.
- Similar systems, in Perl and outside
2008-03-14 08:28:54 tbrannon [Reply]
What Robson calls "Reverse Callback Templating" is very similar to what Terence Parr called "Push style templating" in his paper on the subject:
http://www.cs.usfca.edu/~parrt/papers/mvc.templates.pdf
Within Perl, there is a module very similar to Template::Recall, my own HTML::Seamstress:
http://search.cpan.org/dist/HTML-Seamstress/
A recent thread on perlmonks shows some comparative code between Seamstress and Template Toolkit:
http://perlmonks.org/?node_id=669956
And finally, a list of all push-style templating systems that I have discovered is listed here -
http://perlmonks.org/?node_id=674225
Please add more if you know of them!
- All pipeline models can be used nearly the same was as recall
2008-03-14 08:13:27 rhandom [Reply]
HTML::Template, Template::Toolkit, and any other pipeline system can be used similarly to Template::Recall. In fact, Template::Toolkit or Template::Alloy can be used the same if the EXPOSE_BLOCKS option is set.
use Template::Alloy;
my $t = Template::Alloy->new(
EXPOSE_BLOCKS => 1,
INCLUDE_PATH => "/tmp",
);
$t->process("bar.html/header", {title => 'MyStore', date => scalar(localtime)});
for my $good ('oxfords,Brown leather,$85,0',
'hiking,All sizes,$55,7') {
my @attr = split(/,/, $good);
my $quantity = $attr[3] eq '0' ? 'Out of stock' : $attr[3];
my %row = (
shoe => $attr[0],
details => $attr[1],
price => $attr[2],
quantity => $quantity,
);
$t->process("bar.html/product_row", \%row);
}
$t->process("bar.html/footer", \%row);
The template looks nearly the same except it has TT style tags -
[[% BLOCK header ~%]
<html>
<head>
<title>my site - [% title %]</title>
</head>
<body>
<h4>The date is [% date %]</h4>
<table border="1">
<tr>
<th>Shoe</th>
<th>Details</th>
<th>Price</th>
<th>Quantity</th>
</tr>
[% END %]
[% BLOCK product_row ~%]
<tr>
<td>[% shoe %]</td>
<td>[% details %]</td>
<td>[% price %]</td>
<td>[% quantity %]</td>
</tr>
[% END %]
[% BLOCK footer ~%]
</table>
</body>
</html>
[% END %]
The nice thing with TT and Template::Alloy and all of the other pipeliners is that you caching for free and you get templating systems that have matured over many years.
On a separate note - the issue of forcing pure separation of looping/conditionals from the template and placing them only in Perl code is pedantic rather than practical. It is an interesting idea - but I have never found an html designer that couldn't grasp the concept of looping / conditional behavior in template mini-languages.
- All pipeline models can be used nearly the same was as recall
2008-03-14 17:13:24 James Robosn [Reply]
Sure. I won't argue that one template system can't do another's job. It's just that some template systems fit certain situations better, or appeal to some programmers rather than others. I think more choice is better than less.
I hope that something other than mere pedantry can be found in Template::Recall. I end up being both the designer and the developer on plenty of projects. So it's quite practical for a person in this situation (especially if they see themselves as programmer first) to drive as much as they can from the code.- All pipeline models can be used nearly the same was as recall
2009-02-15 11:22:07 psteer [Reply]
Thanks a lot help me much for our project, done 4 templates - easy to do. example (http://www.my-rootnet.com/)
- All pipeline models can be used nearly the same was as recall
2008-03-17 07:34:00 rhandom [Reply]
The argument of pedantic vs practical is focused at the idea of "no logic whatsoever in the template" which has been espoused by the designers of many template systems - and was not focused directly at Template::Recall.
The restriction of template capability generally results in more code overall and often results in hurdles having to be jumped through because the logic can't be applied directly at the location that needs it.
However, the world is not so small that there can't be place for another Templating system. I wish you all the best in design and development of Recall!
- All pipeline models can be used nearly the same was as recall
- All pipeline models can be used nearly the same was as recall







