Advanced HTML::Template: Filters
by Philipp Janert
|
Pages: 1, 2
Enabling Custom Tags Using Filters
This is where filters come in. A filter is a subroutine called on the template text before tag substitution takes place. A filter can do anything. In particular, it can modify the template programmatically.
A filter is precisely the hook into the template processing that is necessary to enable custom tags. Write your template in terms of standard template tags and your own custom tags. Then provide a filter (or filters) to replace these custom tags with the appropriate combinations of standard tags. The HTML::Template "engine" then does the final substitutions and renders the ultimate output.
For the library example, suppose that you have chosen to use a custom tag <CSTM_DUEDATE> to display the optional due date. This makes the template look very clean and simple:
<ul>
<TMPL_LOOP NAME=books>
<li><TMPL_VAR NAME=title> <CSTM_DUEDATE>
</TMPL_LOOP>
</ul>
The code to set the template parameter values does not change, but you now need to define the filter (and note that you need to escape the slash in the </TMPL_IF>):
sub duedate_filter {
my $text_ref = shift;
$$text_ref =~ s/<CSTM_DUEDATE>/<TMPL_IF NAME=duedate>Date due: <TMPL_VAR NAME=duedate><\/TMPL_IF>/g;
}
Finally, you need to register this filter, so that the module will call it before tag substitution takes place. Filters are options to the template object, so registration takes place when you construct the template:
$tpl = HTML::Template->new( filename => 'books.tpl',
filter => \&duedate_filter );
That's the basic idea. If you wanted to, you could register a sequential collection of filters (one filter per custom tag?) or you can put all required substitutions into a single routine. I can certainly perceive of the possibility of developing reusable "tag libraries" as Perl modules with filter definitions. Interestingly (and in contrast to JSP), the template files themselves do not need to know about the definitions and behavior of the "custom tags." I think this is exactly right: the templates are plain ("dumb") text files, which define the presentation layer. All the behavior is in the Perl code, where it belongs.
Data-Sensitive Displays and Data Filters
Consider another example application. Rather than dealing with optional information, this one uses conditional formatting.
Assume that you have a list of accounts, and each account has a various state: "Good," "Bad," and "Canceled." You would like to display the account number in a different color, depending on the state: green for "Good," yellow for "Bad," and red for "Canceled."
If you tried to do this using <TMPL_IF> tags, this would be a real mess, for two reasons: You have more than two choices, so that we can't use a <TMPL_IF>...<TMPL_ELSE>...</TMPL_IF> construct; instead you would have to use a sequence of individual conditionals: <TMPL_IF>...</TMPL_IF><TMPL_IF>...</TMPL_IF><TMPL_IF>...</TMPL_IF>. Moreover, <TMPL_IF> does not allow for arbitrary conditional expressions (such as $status eq 'good'). All it does is test a Boolean variable. The classic approach is to introduce three Boolean dummy variables: $isGood, $isBad, and $isCancelled to use in these tests. There's certainly a better way!
Instead, introduce a custom tag that allows for configurable formatting:
<CSTM_SPAN NAME=...>
Here is the associated filter:
sub span_filter {
my $text_ref = shift;
$$text_ref =~ s/<CSTM_SPAN\s+NAME=(.*?)\s*>
/<span class='<TMPL_VAR NAME=$1_class>'>
<TMPL_VAR NAME=$1>
<\/span>
/gx;
}
If you use this custom tag in your template as <CSTM_SPAN NAME=account>, then the template in the intermediate state, after filter application, but before template parameter substitution, will look like:
<span class='<TMPL_VAR NAME=account_class>'>
<TMPL_VAR NAME=account>
</span>
The original single custom tag has expanded into an HTML <span> tag wrapping the actual dynamic content. The <span> defines a CSS class to provide formatting to the dynamic content. The name of the CSS class itself is dynamic, too--so that you can select the classes .bad or .good depending on the value of the account status. The name of the CSS class comes from the name of the displayed parameter. If this class is defined in a stylesheet or within the HTML document itself, a CSS-capable client will apply it to the dynamic text as expected.
In other words, the innocuous <CSTM_SPAN> tag actually requires two parameters: one containing the text to display and the other specifying the CSS class to apply. All you need to do is to set the account_class parameter to the appropriate value in Perl code (and, of course, define the CSS classes somewhere in the stylesheet.)
Here is a slick way to stay in control doing this. You can call the param() function either with individual name/value-pairs (as I have shown before), or with a hash-ref: $tpl->param( { key1 => value1, key2 => value2 } ). In other words, rather than calling param() repeatedly throughout your code, setting individual parameters, you can build up one large hash, containing all parameters, and then pass it to the template in a single call.
Now you can apply the filter trick that worked so well for the template itself, on the parameters as well! In other words, before calling $tpl->param( \%parameter_hash), pass the hash to a subroutine, which performs data filtering operations on the parameters. In this case, it adds the CSS class appropriate for the account status for each account in the data structure.
This approach centralizes all data-dependent display decisions in a single subroutine. If you add further status codes, or if you want to change the display class, there is only a single location to edit.
Here is a Perl program demonstrating these concepts:
#!/usr/bin/perl -w
use HTML::Template;
# Create the template object
my $tpl = HTML::Template->new( filename => 'tmpl4.tpl',
filter => \&span_filter );
# Build a data structure containing all the parameters
my %params = ( title => "Accounts",
accounts => [ { account => 'Good' }, { account => 'Bad' } ] );
apply_display_logic( \%params );
# Set all parameters for the template at once
$tpl->param( \%params );
# Print
print "Content-Type: text/html\n\n", $tpl->output;
# ---
sub span_filter {
my $text_ref = shift;
$$text_ref =~ s/<CSTM_SPAN\s+NAME=(.*?)\s*>
/<span class='<TMPL_VAR NAME=$1_class>'>
<TMPL_VAR NAME=$1>
<\/span>
/gx;
}
sub apply_display_logic {
my $hash_ref = shift;
my %account_classes = ( Good => 'good', Bad => 'bad' );
foreach my $acc ( @{ $hash_ref->{ accounts } } ) {
$acc->{account_class} = $account_classes{ $acc->{account} };
}
}
#%@$! Happens
When I first explained this idea to a colleague, his reaction was: "Great. You go down that path and you are going to end up with JSP. Is that what you want?" To which my response was: "Well, fertilizer happens."
GUI development is a pain. Always. You cannot avoid all issues regarding the separation of presentation and logic in GUI development. The challenge in my opinion is therefore not to find the ideal web development framework, but the optimal one for the task at hand.
HTML::Template is very, very good for rather simple, minimum fuss, straightforward websites (of which there are significantly more than anyone might want to admit). It is particularly well-suited for reports and stats. In this application domain, the ability to the define custom tags to take care of formatting and special presentation issues is a distinct advantage.
Conclusion
In the second part of this series, I will take these ideas a step further and explore how to use HTML::Template to create GUI-like widgets in a web/CGI situation.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 7 of 7.
- This article kept me awake all night
2007-07-16 20:57:21 AmbroseChapel [Reply]
... no, literally. I couldn't sleep last night because I read this article before I went to bed.
I find it very poorly conceived and written for the following reasons:
1) It assumes that the presence in your template file of conditionals is by its nature a bad thing.
I'm told that if I find stuff like the "if due date" example, I will "start looking for a better solution" because "the template itself is becoming increasingly unwieldy and its structure harder to follow."
I don't find myself looking for another solution. That's a perfectly good bit of code and I don't understand why it offends the author so much. I guess there's no accounting for taste.
But adding insult to injury, he then goes on to show me a "solution" which undeniably makes the template structure harder to follow! It's the very opposite of what he says he's doing.
Which is easier to follow: <tmpl_if></tmpl_if> or <something_i_just_made_up>? Clearly, it's the first. No reasonable person could agree that his <cust_duedate> is easier to understand. It would require documentation, as compared to the built-in syntax which is
a) already documented and
b) self-documenting.
2) His example of how HTML::Template falls down when it comes to a three-possibility variable is completely specious.
There are three possible states of a variable which should result in three different HTML class attributes. His solution is to come up with another completely arbitrary <obscurename> template tag, which *expands* to
<span class='<tmpl_var name=account_class>'>
<tmpl_var name=account>
</span>
but why not just start with that in the first place? What's wrong with this?
<span class='<tmpl_var name=account>_class'>
<tmpl_var name=account>
</span>
He's written a callback sub which requires two parameters and a complicated regex, just to arrive at functionality which is there in HTML::Template in the first place. And he's made his template HARDER to understand.
The key thing which is missing from this article is the understanding that templating systems are most useful when two or more people are working on the same site. One guy works on the HTML, another on the perl.
In the Due Date example, what happens when the HTML guy has to change the text -- the word comes down from management that instead of "Due back on:" it should read "return date for this item:".
Using the "Advanced" techniques here, the HTML guy is screwed. He has to get the Perl guy to do it for him. Guess what? Now you don't have a templating system, you have a programming system. You might as well be using <<HEREDOCs.
The same for the example with the three classes. The author has taken away from HTML coders the ability to change the class into an id, or any one of a number of other things.
Honestly? I don't think this is an "Advanced" use of HTML::Template at all. I think it's the guy's personal use of it, where he looks after both the template and the perl and thinks himself mighty clever. It's actually a step backward and it fatally misunderstands the point of templating systems.
- Ternary Operator
2006-12-12 17:14:25 johnk@riceball.com [Reply]
The use of filters seems to violate the separation between logic and layout, and puts the designer/html coder in the position of waiting on the programmer to create a filter to turn one little bit of text on and off.
Perhaps there's a better solution for simple situations, if you condense the TMPL_IF tag into a single tag. To use mathy-talk:
<TMPL_TERNARY_IF expression="..." ifTrue="..." ifFalse="..." />
Or, to be sane about it, just use the ternary operator as-is, just like Perl:
<TMPL_IF $foo ? "Return this value" : "Nevermind" />
Then, you get rid of the closing tag, as well as the code in the filter.
I've used ternary ops in simplified templating engines, and they work well.
The ternary operator is pretty easy to learn. I'd argue that it's easier than the IF tags with nested HTML, because the syntax is condensed. Also, spreadsheets have a similar IF() function that works identically, so the HTML coder might know it already.
To implement more complex logic, what you can do is have the underlying object have a public interface, comprising boolean properties, to tell the template to show relevant text. For example, if you wanted to draw an HTML table, part of the logic might read like this:
<td>
<TMPL_VAR NAME=data />
</td>
<TMPL_IF endOfColumn ? '</tr>' : '' />
$endOfColumn is a boolean that turns to true if we're in the last column.
- Re: Advanced HTML::Template: Filters
2006-12-01 01:44:18 DaveCross [Reply]
"and note that you need to escape the slash in the </TMPL_IF>"
Well, that's only because you've used the default delmiters for s///. You can change them so they don't clash with your data.
s|<CSTM_DUEDATE>|<TMPL_IF NAME=duedate>Date due: <TMPL_VAR NAME=duedate></TMPL_IF>|g
In my opinion that's a little easier to read.
Also, I wondered how you thought HTML::Template compared with the Template Toolkit. I find HTML::Template a bit too restrictive and far prefer the flexibility of TT.
- Re: Advanced HTML::Template: Filters
2006-12-11 11:26:27 eggzeck [Reply]
Very good point made by "DaveCross" (in his above comment about s///).
I believe that one should always use a variant of s/// if you're going to actually use a literal '/' in there. For example:
s{hello}{/hello}
Would be much better than:
s/hello/\/hello/
:-)
- Re: Advanced HTML::Template: Filters
- look my HTML::Template is better then JSP's
2006-11-30 19:09:30 saramic [Reply]
Great, every perl developer can now go off happily thinking that the HTML::Template module with 6 tags that they use is better then JSP's, heck better then the whole J2EE thingy.
Why is it that developers (in particular Perl developers) like to slag off other languges, often languages that they do not know much about. There is nothing stopping them learning these other languages and still happily using perl, when it is appropriate. That is the point, each language fills a certain niche and need. Just like perl's philosophy of doing things more then one way, so other languages provide yet another way to do things and sometimes a better way.
On you attack of JSP's, sure you can stuff things up by putting code directly into a html page but you do not have to. You could use JSP's in the same way that you do with HTML::Template, except rather then passing just a TMPL_VAR or LOOP you can pass a whole object and use accessor methods to get at data you want.
You can also create the equivalent of filters in JSP tag libraries. This is the way I recommend using JSP's from my limited experience. There are even a whole lot of "filters" available already http://jakarta.apache.org/taglibs/ sure most of them do functions (i18n, db connecions, send email) but they could just as easily just format data comming from a server.
so an example of creating a list of months for a select box
<select name="month">
<dt:months id="mon">
<option value="<jsp:getProperty name="mon" property="monthOfYear"/>">
<jsp:getProperty name="mon" property="month"/>
</dt:months>
</select>
is equivalent to
<select name="month">
<TMPL_LOOP NAME='months'>
<option value="<TMPL_VAR NAME='month'>" name="mon">
</TMPL_LOOP>
</select>
but the perl needs me to generate the months of the year in the perl code (which is ok) then I can just override the given tag lib or create my own to have custom styling for winter and summer months etc (just like a perl filter) and everything looks not all that much different just different language and syntax.
Please do not lower yourself (and loose a little of your reputation) by slagging off a language. it is not worth it, they are all good languages. Now teach us some more perl.
cheers saramic
PS: I appreciate the tutorial on filters and it will come in handy.
- Delimeters
2006-11-30 17:23:17 sigzero [Reply]
The only thing I would wish for is more choices with delimeters. The default tag style looks unfinished and the HTML comment one just doesn't look right. I wish there was a third like ASP one <% %>.
Other than that HTML::Template is really really good.- Delimeters
2006-12-01 14:00:08 kilgore12 [Reply]
Well use a filter to change your delimiter! I use [% and %]
- Delimeters



