Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Trying to understand template-foo

by jest (Pilgrim)
on Sep 11, 2003 at 21:07 UTC ( [id://290823]=perlquestion: print w/replies, xml ) Need Help??

jest has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

I've previously been writing code on an as-needed basis, mostly handrolled stuff, especially for things like Web access to databases. Recently, as I've been writing more and more of it, and spending half the day either cutting-and-pasting code or just reinventing the wheel every day, I've been looking more seriously at various systems for streamlining this work, including Class::DBI, CGI::FormBuilder (which in particular is impressing the hell out of me so far, I must say), and various templating systems. Yes, I was originally inspired to think about this because of Kake Pugh's How to Avoid Writing Code article at perl.com.

I'm not asking about which templating system I should use; I know there are tons of nodes about that already here, many of which I've found helpful. (I'm most interested in HTML::Template and Template Toolkit.) Rather, what I'm stuck on is sort of the nature of templating itself. Up to now everything I've read and studied has been based on CGI.pm and the like for HTML generation, with everything controlled by Perl in terms of how the format is created. But looking at the examples of templating in action, I'm unsure of what sort of things should be handled in code, and what should be handled by the template.

For example,

I have a program which queries a database of wine and prints out the contents of a collection. I go line-by-line with the wines, keeping track of when new countries/regions are encountered so I can change the headers (with CSS), and keeping count of quantities so I can display them as running totals. The code that generates this, starting from a returned database statement handle, looks like this:
push (@row, Tr( td({-width=>"10%"},u(b("Bottles Remaining"))), td({-width=>"*"},u(b("Wine"))) )); while (my $ref = $sth->fetchrow_hashref()) { my $country = escapeHTML($ref->{'country'}); if ($country ne $oldcountry) { push (@row, Tr(td({class=>"regioncount"}," $regioncount Total for + region"))) unless $regioncount == 0; # skip first pass push (@row, Tr({class=>"countrycount"},td("$countrycount Total fo +r country"))) unless $countrycount == 0; # skip first pass push (@row, Tr(td({class=>"countryheader"},$country))); $countrycount = 0; $oldregion = "bar"; # clear region when country changes } $oldcountry = $country; my $region = $ref->{'region'} ? escapeHTML($ref->{'region'}) : "Othe +r"; if ($region ne $oldregion) { push (@row, Tr(td({class=>"regioncount"}," $regioncount Total for + region"))) unless ($regioncount == 0) or ($countrycount == 0); # skip first + pass push (@row, Tr(td({class=>"regionheader"},$region))); $regioncount = 0; } $oldregion = $region; $regioncount += $ref->{'number_left'}; $countrycount += $ref->{'number_left'}; $totalcount += $ref->{'number_left'}; my $wine_name = a({-href=>"wine_display.cgi?id=$ref->{id}"}, escapeHTML($ref->{'wine_name'})); push (@row, Tr({-class=>"$rowcolor"}, td({-class=>"body"},$ref->{'number_left'}), td($wine_name) )); $rowcolor = ($rowcolor eq "lightrow" ? "darkrow" : "lightrow"); } push (@row, Tr({class=>"regioncount"},td("$regioncount Total for regi +on"))); push (@row, Tr({class=>"countrycount"},td("$countrycount Total for co +untry"))); push (@row, Tr({class=>"totalcount"},td("$totalcount Total in cellar" +))); print table({-width=>"95%", -cellspacing=>"0"},@row);

There are a number of things I'm unclear about in terms of porting this code to a templating system. On the basic level, there's stuff like how to use CSS with it (the tutorials seems to spend less time than I would prefer describing the use of CSS with these systems, which is difficult because I'm new to CSS too), and how differently this might work with Class::DBI. And there are individual questions, like how you're meant to do stuff like switching the row color on alternate lines.

But on a larger level, how much of this is supposed to handled by the templating system, and how much with Perl? Is this something that's going to end up being two lines of Perl, and the rest handled by Perlish code in the templating system? (This is how it's often done in the example code I've seen.) Or is it going to look very close to how it does now, except with template stuff instead of CGI.pm functions? My personal preference would be to do as much as possible in Perl, because that's what I know and that's what I think would be most portable--it's also why I prefer HTML::Template and TT to things like HTML::Mason. But perhaps that's defeating the purpose of using templates. This is all for personal or small-scale use, I don't have to pass things off to a design staff. But I'd like to get better control over presentation without losing control over my Perl. So how should I be thinking of this in my development as a programmer?

Replies are listed 'Best First'.
Re: Trying to understand template-foo
by perrin (Chancellor) on Sep 12, 2003 at 04:00 UTC
    Hi, I think I talked to you on the Class::DBI mailing list. I just wrote up a big long response and then lost it when I accidentally closed the browser. I don't have the patience to type it all in again, but here are the major points:

    • Read some articles about using the model-view-controller pattern with Perl. You can find some on here, and there are some on perl.com, including one that I wrote.
    • Imagine that your code has to produce a data structure with zero HTML in it so that it can be used on a dozen different sites with different layouts, including one that uses PDF and one that is plain text e-mail.
    • In yur example, I think you'll have a list of countries each of which contains a list of regions, each of which contains a list of wines. Each country, region, and wine will end up being a hash. Those hashes could potentially be represented as objects, and you could make Class::DBI classes for them, although in your example you have used a single large query instead of traversing object relationships to get the data.
    • Figure out how to make your code produce that data structure instead of producing HTML.
    • Write a template that takes that data structure and produces the HTML you want.
    • You want the template to be as simple and as dumb as possible.
    To answer your question about CSS, it's mostly irrelevant. The templates can contain any HTML you want including CSS. The important thing is that they only get to look at the data, not change any of it.

    I hope that gets you started. If you come to a New York Perl Mongers meeting I can show you how to do it in detail.

      Thanks to all who replied; I've been considering everyone's comments and it's all been helpful. Which isn't to say that I now know exactly what I'm doing....

      For now I followed mostly Perrin's advice, and rewrote the included code to instead generate a complex data structure that I passed to a Template Toolkit template. I kept all the summary calculations in the Perl, so the only programming-ish things in the template were FOREACH loops and a few conditionals to handle things like the alternate-colored lines and such.

      It works perfectly, i.e. exactly the same as it was before but more modifiable. I must say that I still feel a little uncomfortable with it--the programming in the template, while hardly complex, is still programming, and it's distracting to have a different way of handling looping, comparisons, and data structures. But I'm learning, and TT has so much neat stuff that I concede it's probably worthwhile to know more about it in the long run.

      Thanks again for the ideas. Next stop, on to Class::DBI!

Re: Trying to understand template-foo
by Roger (Parson) on Sep 11, 2003 at 23:46 UTC
    This is an interesting problem, isn't it? I remembered I had similar paradox when I was working in the bill printing industry, when I had to choose between a template driven model and data driven model for the new bill printing engine.

    I chose the template driven model that populates the contents of the bill via data callbacks, because I believe the less cohesion between the look & feel of the form and data calculation/processing, the better is the maintainability of the project. (We had artists to design form templates and programmers to do data merge programs.)

    For example, the bill template can have table variables, simple lookup's, complex paragraph lookup's, calculations, etc., each identified by a unique name/id. The data feeding code has a simple callback that matches the unique name/id of the form variable with a set of business rules.

    The beauty of this model is that the form design and prototyping can go ahead before any programming is done (because the package contains a template designer and previewer), and the programmer is simply required to generate required data according to variable name/id on the form according to some business logic. And of cause changing the position of an item on the form can be done by the artist, without any programmer input. This cuts down the development cycle of a bill by more than two-three folds.

    The template driven model can be applied to all kinds of different output templates - HTML, XML, Postscript, Simple E-mail, etc., without major rework.

    To balance between how much coding and how much template for the project is an art, a joy, isn't it? It all depends on the nature of project, the complexity of the form, etc. A successful design, an efficient design can deliver great satisfaction to everyone working on the project.

Re: Trying to understand template-foo
by jdtoronto (Prior) on Sep 12, 2003 at 02:40 UTC
    Hi jest

    You are facing the dilemna which faces every Perl coder who has also to do CGI type scripts. Until recently I generated a lot of my HTML using CGI.pm. But, as you have found, it becomes unwieldy and means that every time you want to change a design element you need to dig into the Perl. So, time for templating.

    My feeling is that the design problem should be handled in the design phase and that presentation, should as far as possible, be delegated to the template.

    This means that you can then effectively use CSS (Get 'Cascading Style Sheets - The Definitive Guide' by Eric Meyer from O'Reilly and 'Eric Meyer on CSS' by Eric Meyer from New Riders, they are an excellent introduction and reference). By doing this you can almost totally divorce the presentation design from the Perl coding. In my own case, I present a lot of dynamically generated forms that write back to databases. They are all perform the same function but I now have just one Perl script that takes a page template that also loads a form template. I now use one form template which has its presentation controlled by a style-sheet with as many as 30-50 different 'content wrappers' or page templates in any given week. These forms at times handle as many as 50,000 completed forms per week.

    To me HTML::Mason is not a templating system, it is a code embedding system which tends to make a Perl programmer feel a lot more like a PHP programmer. I write in Perl becuase I want to write in Perl, not PHP! Mind you, there is nothing wrong with PHP, but why use a silly little band-aid when you can do it properly with duct-tape :)

    Using Class::DBI and some of the other DBIx series modules can take a lot of heay out of the whole process. CGI.pm is still very useful glue, CGI::Session gets a good rating in this office also.

    I spent a lot of time reading the various documents, there are some useful things on this site in particular and also at least one good paper on the mod_perl site about choosing a templating system. Then I started coding, like you my work is getting stuff into and out of databases using Perl, DBI, MySQL and either HTML or in my case a lot of Tk. Test code some applications in each of the different systems, thats the best way to learn!

    But I have achieved almost total separation of presentation and data-processing by using HTML::Template, DBI::Class, CGI.pm and CGI::Session.

    Of course, as always, YMMV!

    jdtoronto

Re: Trying to understand template-foo
by cfreak (Chaplain) on Sep 12, 2003 at 13:36 UTC

    I use HTML::Template because its simple and fast and easy for designers to understand. I think the key about doing templates is rather than think of them as code that controls your formatting, think of them as a formatted page where you can control the content.

    To help you along here's a simple example that could be used for your wine website

    my @rows = (); # H::T can create a looping structure out of # an array of hashes # the keys for each hash are the variable # names for each row of the loop # add database stuff here while(my $ref = $sth->fetchrow_hashref()) { # you can mangle whats in ref, I often # just push the ref onto the array and try to # put my already mangled stuff in the database # This only works though if the variable names in # the template are the same as your DB column names push(@rows,$ref); } my $template = HTML::Template->new(filename=>'/path/to/some.tmpl'); # H::T expects a reference to the array $template->param(wines=>\@rows); # print your header (CGI or CGI::Simple is still useful for this) print $cgi->header(); # and then your H::T output print $template->output();

    Then in your template file, assuming you have wine_name, bottles and country ... it might look something like this:

    <table cellspacing="3" cellpadding="3" border="1"> <tr> <td><b>Wine</b></td> <td><b>Country</b></td> <td><b>Bottles</b></td> </tr> <tmpl_loop name=wines> <tr> <td><tmpl_var name=wine_name></td> <td><tmpl_var name=country></td> <td><tmpl_var name=bottles></td> </tr> </tmpl_loop> </table>

    Your Perl code will add a new row for each element in your @rows array with each of the variable names as <td> areas. There's tons of other stuff useful for doing things like changing the background color for every other row, etc. Have a look at the loop_context_vars option in the H::T docs for that.

    I hope that gives you an idea of how to do it.

    Lobster Aliens Are attacking the world!
Re: Trying to understand template-foo
by xtype (Deacon) on Sep 13, 2003 at 21:37 UTC
    "like how you're meant to do stuff like switching the row color on alternate lines."

    Admittedly this is a very small point in your questions, however, I already had the file open, so I thought, why not cut and paste this for you... knock off the small ones. :^)

    perl:
    my $bgcolor = "#d7d7d7"; for(@{$item_ref}) { $bgcolor = ($bgcolor eq "#d7d7d7") ? "#f7f7f7" : "#d7d7d7"; push @{$nodes}, { title => $q->escapeHTML($_->{'title'}), text => $_->{'text'}, inid => $_->{'inid'}, timestamp => localtime( epochtime($_->{'timestamp'}) ), bgcolor => $bgcolor, }; }
    template:
    <table border="1" cellpadding="2" cellspacing="0"> <!-- snip --> <tmpl_loop nodes> <tr bgcolor="<tmpl_var bgcolor>"> <td align="right"><tmpl_var title></td> <td rowspan="2"><tmpl_if inid><a href=?idisplay&img=<tmpl_var inid +> target=_blank><img src=?getimage&img=<tmpl_var inid>&thumb border=0 +></a><tmpl_else>&nbsp;</tmpl_if></td> </tr><tr bgcolor="<tmpl_var bgcolor>"> <td colspan="3">Last modified <tmpl_var timestamp> by <tmpl_va +r user></td> </tr><tr bgcolor="<tmpl_var bgcolor>"> <td colspan="4"><tmpl_var text></td> </tr> </tmpl_loop> </table>

    Possibly there is a cleaner/better way, if so I am sure I will hear about it.

    ciao, -xtype

    update: Actually, you have caused me to re-evaluate that. You should keep the colours in the template or the style sheet, not in the perl!
    So instead do something like this:
    <tr bgcolor="<tmpl_if switch_colour>#d7d7d7<tmpl_else>#f7f7f7</tmpl_if +>"> or <tr id="<tmpl_if switch_colour>every<tmpl_else>other</tmpl_if>">
Re: Trying to understand template-foo
by idsfa (Vicar) on Sep 16, 2003 at 06:27 UTC

    The breakpoint for me has been the number of data consumers whom I am required to support with my perl code. Generally, I write a chunk of code to gather data which helps me do my job. Later, my PHB notices that I have this information and asks me to cook it to produce a report which he can use for politics (budget/staffing/what-have-you). Then his peers/bosses ask for further enhancements for their own political maneuverings and you end up being asked for calculations which make no sense from people you don't even know.

    The break point for me has depended upon the uncontrolled data consumers. When I have a single data consumer, templates are overkill. When I have a handfull of relatively similar consumers, HTML::Template does the job just fine. If the community grows too large or too unwieldy, I prefer to dump the data into a platform neutral format (usually with XML::Writer) and make them pony up the resources for formatting (XSLT).

    Then you crush them later with the raw data. ;-)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://290823]
Approved by Enlil
Front-paged by Enlil
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (4)
As of 2024-04-20 00:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found