Petal::Cookbook(3pm)
π Petal::Cookbook π
okay bestie, welcome to the Petal::Cookbook β this is the <3pm> manual page for it, from the User Contributed Perl Documentation corner of the universe. It's literally just a big pile of recipes for building templates with Petal. Grab a snack, we're cooking. π
β¨ NAME β¨
Petal::Cookbook β recipes for building templates with Petal. That's it, that's the whole vibe. π
πΈ DESCRIPTION πΈ
This doc is a lil collection of examples showing how people actually use Petal templates. Most of them are about using Petal to turn HTML templates into HTML files, because that's the main character use case here. π
π BASICS π
π¦ Template location π¦
okay so if you're using Petal for web app development, your templates should not be reachable by the webserver. Like, at all. fr fr. It could genuinely be a security risk if they're available, because there might be code or comments in there that users are not supposed to see before Petal processes everything. π¨
So the recommendation is: store your templates in a directory that's not web accessible. Personally the author prefers putting the directory outside of the web root, but you could also use permissions or .htaccess files to lock down access to the directory. Either way, that directory path should go into the $Petal::BASE_DIR global setting, or into the 'base_dir' argument for the new() constructor. π«Ά
π§ Template naming π§
Petal literally does not care what you name your template files. Petal is unbothered, moisturized, in its lane. π
Personally, the author likes naming templates with the .tmpl extension, because it helps distinguish templates from static html β good for both humans and designers. But some GUI editors will not open files without a htm/html extension (esp. under Windows), so keep that in mind. π
β Fixing invalid templates (Is this XML well-formed?) β
If you're getting a parse_error when you try to process your template, you're gonna need to clean up your XHTML template so Petal can actually process it. Two tools will be of great assistance in taking that step towards better standards compliance β HTML Tidy (<http://tidy.sf.net>) and xmllint. On top of that, you can use the page validation services at W3C (<http://validator.w3.org/>). Or, alternatively, you could use the Petal::Parser::HTB module, which will parse non well-formed HTML documents using HTML::TreeBuilder. options options options, we love to see it. β¨
HTML Tidy will rewrite your document into valid XHTML and, if you ask it nicely, even replace legacy formatting tags with their CSS counterparts. You can safely ignore the warnings about proprietary attributes. But be sure to read the output of what HTML Tidy is doing, or else you may find it removing important tags that it thinks are empty or invalid (e.g., inline elements outside of a block). π One of the important options that should be set is output_xhtml (-asxhtml from the command-line). Here's an example of how to use it (see the documentation for complete details):
tidy --asxhtml original_file.html > new_file.html
Once your document is well-formed, you can use xmllint to do day-to-day checking that it stays well-formed, without having to wade through all the warnings HTML Tidy generates about proprietary attributes. The following command will check that a document is well-formed:
xmllint --noout <filename>
To prevent errors about undefined namespace prefix, be sure to include these in your template like so:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:petal="http://purl.org/petal/1.0/"
xmlns:metal="http://xml.zope.org/namespaces/metal">
You may get errors from xmllint about unknown entities such as . These can be safely ignored, though you can use the numeric version   instead to keep xmllint happy. If you find a way to suppress these warnings, please let us know. In the meantime, you can pass the output through grep to ignore these bogus warnings:
xmllint --noout tmpl/contact_info.tmpl >& grep -v 'Entity'
Now you have no excuse for not creating well-formed XHTML documents. periodt. π
π Passing a hashreference to Petal::process π
An effective way to collate data to send to the Petal process command is via a hash reference. Used as follows, this technique lets you build up your data to be passed to the template slowly, piece by piece, like a little data croissant:
my $hash = { string => 'Three', 'number' => 3 };
$hash->{'foo'} = "bar";
my $template = new Petal ( 'test.tmpl' );
my $html = $template->process($hash);
# Output the results
print "Content-type: text/html\n\n";
print $html;
π Basic loops with tal:repeat π
One way to use tal:repeat is to create a reference to an array of anonymous hashes. Here's an example:
my $array_ref= [
{ firstname=>"David",
surname=>"Lloyd"
},
{ firstname=>"Susan",
surname=>"Jones"
}
];
With this array you can use the tal:repeat structure. Let's say you have this template β this is a snippet so don't forget the proper name space declarations and such:
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
<tr tal:repeat="name names/list_of_names">
<td tal:content="name/firstname">First Name</td>
<td tal:content="name/lastname">Last Name</td>
</tr>
</table>
If you processed that template and the method call "list_of_names" returned an array reference as described above, you'd get:
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
<tr>
<td>David</td>
<td>Lloyd</td>
</tr>
<tr>
<td>Susan</td>
<td>Jones</td>
</tr>
</table>
So, in a tal:repeat construct:
tal:repeat="tal_variable_name EXPRESSION"
tal_variable_name is the name of the variable you use in your tal template to refer to each row of data you are looping through. π
EXPRESSION should return an array reference, where each row is an anonymous hash, array, scalar or object.
You can then refer to the members of the anonymous hash like this:
"$tal_variable_name/key_from_hash"
π₯ INTERMEDIATE TIPS π₯
π Assigning attributes (submitted by Warren Smith) π
Up until now, if you wanted to use petal to pre-select an item in a selectbox, you'd have to do each item twice, like so:
<select>
<div petal:repeat="option options">
<option petal:condition="true: option/selected" petal:attributes="value option/value" petal:content="option/label" selected="selected">Option 1</option>
<option petal:condition="false: option/selected" petal:attributes="value option/value" petal:content="option/label">Option 2</option>
</div>
</select>
$VAR1 = [
{ value => 1, label => 'Option 1', selected => 1 },
{ value => 2, label => 'Option 2', selected => 0 },
{ value => 4, label => 'Option 3', selected => 0 },
];
After reading the Petal source, Warren found that if you use petal:attributes to assign an attribute an undefined value, the attribute gets omitted. So the above code can be replaced with the simpler version below:
<select>
<option petal:attributes="value option/value; selected option/selected" petal:content="option/label">Option 1</option>
</select>
$VAR1 = [
{ value => 1, label => 'Option 1', selected => 1 },
{ value => 2, label => 'Option 2' },
{ value => 4, label => 'Option 3' },
];
It turns out that although this isn't documented in Petal's documentation, this behavior is actually part of the TAL specification:
http://www.zope.org/Wikis/DevSite/Projects/ZPT/TAL
Thanks to Fergal Daly for his knowledge of the TAL specification. π«Ά
π Generating even/odd rows (submitted by Warren Smith) π
Warren developed a decode: modifier that works similar to Oracle's decode statement. It provides an if/then/else construct and is part of the Petal::Utils collection of modifiers. Using decode, it's possible to make even/odd rows of a table different classes, which allows you to do things like alter color, font-size, etc β relatively easy, slay. π
Example:
<table>
<tr tal:repeat="emp employees" tal:attr="class decode: repeat/even 1 'even' 'odd'">
<td tal:content="emp/name">Employee Name</td>
...
</tr>
</table>
See Petal::Utils for more information.
Alternatively, this can be done entirely with TAL (contributed by Jonathan Vanasco):
<div tal:repeat="row rows">
<tag tal:omit-tag="string:1" tal:condition="repeat/even"><tag
tal:define="rowClass string:rowEven" tal:omit-tag="string:1"/></tag>
<tag tal:omit-tag="string:1" tal:condition="repeat/odd"><tag
tal:define="rowClass string:rowOdd" tal:omit-tag="string:1"/></tag>
<div
tal:attributes="class rowClass"
>
This will use either the rowEven or rowOdd class. All of the 'tag'
elements are omitted on render. This uses a nested define tag in a
condition tag, because define precedes condition in order of operations.
</div>
<div>
The simplest way to do odd/even rows may be to duplicate the code entirely for each type of row, though this may cause maintenance headaches:
<table>
<tr tal:repeat="emp employees">
<td tal:condition="repeat/odd" class="odd" tal:content="emp/name">Employee Name</td>
<td tal:condition="repeat/even" class="even" tal:content="emp/name">Employee Name</td>
...
</tr>
</table>
β¨ ADVANCED β¨
πΈ Invoking methods on objects πΈ
Okay bestie, this is the part where Petal gets lowkey powerful. Petal can call methods on your objects if you pass them in through the %hash when you run Petal::process. Say you wanna check if a particular record lives inside a recordset that came back from an SQL query β you can use OO-Perl techniques, and Jean-Michel described the whole vibe like this:
- all your records are hashrefs which come from some database
- you have a list of them to display
Let's say that the database table looks like this:
Raters (id, first_name, last_name, relation, phone, email)
You could bless each record into a package as is:
use MyApplication::Record::Rater;
my @records = complicated_query_somewhere_else();
bless $_, "MyApplication::Record::Rater" for (@records);
$hash->{'records'} = \@records;
Your module could look like that:
package MyApplication::Record::Rater;
use strict;
use warnings;
use CGI;
use Carp;
sub is_current_id
{
my $self = shift;
my $cgi = CGI->new;
my $id = $cgi->param ('rater.id');
return unless (defined $id and $id and $id =~ /^\d+$/);
return $id == $self->{id};
}
1;
Then on top of your existing data, you have a method which you can call from Petal, i.e.
<ul petal:repeat="record records">
<li petal:condition="true:record/is_current_id" petal:content="string: Current id = $record/id">Current id</li>
</ul>
This trick can also be used when you have foreign keys in database fields. π
<fictious_scenario>
For example, let's imagine that you have a column called 'friend_id'. It references another 'rater' which is supposed to be a friend of that person. π the drama
You could define the following subroutine:
# give me the friend record for that person
sub friend
{
my $self = shift;
my $friend_id = $self->{friend_id};
my $sql = 'select * from rater where id = ?';
my $sth = $::DBH_CONNECTION->prepare_cached ($sql);
$sth->execute ($friend_id);
my $hash = $sth->fetchrow_hashref;
return unless (defined $hash);
bless $hash, "MyApplication::Record::Rater";
return $hash;
}
Then in your template, you could do:
<div petal:if="true:rater/friend">
Your friend is: <span petal:content="string: $rater/friend/first_name $rater/friend/last_name">First Last</span>
</div>
</fictious_scenario>
Thanks to Jean-Michel Hiver for this tip. π«Ά
If you are doing a lot of database manipulation via Petal, you probably should consider an object-relational mapping library . Personally, I recommend Class::DBI. There is a list of many of these tools at Perl Object Oriented Persistence (http://poop.sourceforge.net/). ngl the URL is giving chaos but it's real, iykyk π
π Using CGI.pm to build forms π
Calling the HTML generating methods of CGI.pm from the Petal template provides an extremely simple means to develop forms. For example, the ususal ratnest of loops used to populate a checkbox group can be replaced by the simple and elegant construct below. You can put in a dummy checkbox to give the HTML designer something to look at. Be sure to call CGI with the -compile option as follows:
use CGI qw(-compile [:all]);
$hash->{'query'} = new CGI;
$hash->{'choices'} = [1, 2, 3, 4];
<span petal:replace="query/checkbox_group 'Choices' choices '' 'true'">
<input name="Choices" type="checkbox" value="test">Test</input>
</span>
Thanks to Kurt Stephens for this tip. β¨
π COPYRIGHT π
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
All code examples in these files are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required. (bestie this is a gift, take it, it's yours π)
πΈ AUTHOR πΈ
William McKee <william@knowmad.com>.
Thanks to the following contributors: Jean-Michel Hiver, Kurt Stephens, Warren Smith, Fergal Daly. π«Ά absolute legends, every single one.
π¦ SEE ALSO π¦
PetalPetal::Utils- the test file
t/084_Cookbook.tand the test templatet/data/cookbook.html
and that's the whole cookbook, bestie β you ate that π β¨
This page is from perl v5.36.0, dated 2022-11-19, and lives at Petal::Cookbook(3pm). Go forth and template, my love. ππ
π¦ where this page comes from
- debian bookworm: libpetal-perl 2.26-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- debian trixie: libpetal-perl 2.26-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libpetal-perl 2.26-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libpetal-perl 2.26-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libpetal-perl 2.26-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms