PetiteCGI::Template
Please note that this documentation applies to version 2.0b10, 24th of August 2003. The module is an Beta release.
The documentation is under revision.
Background
One of the most common problems in developing web-based applications is how to separate content, structure and layout. The Template library provide methods for doing just that.
Table of Contents
- Usage
- Variables
- Callbacks
- Content Inclusion
- Default Values
- Development History
- Known bugs
- Things to do
- Download
Usage
Using PetiteCGI::Template is a matter of replacing some parts in your
HTML - or anything else
for that matter1 - with module specific
hooks
, and doing some Perlish mumbo-jumbo to get the two bits
working in concert.
... in templates
The general syntax for use in the template files look like this:
<!-- template FUNCTION ATTRIBUTES -->
Whitespace within the template declaration is, for the most part, not significant. The exception is the first line which identifies the comment as important to the template system. The characters double quote (") and single quote (') work as one might expect - though nothing will prevent a template variable to be expanded, even inside a string encased in single quotes (').
Since, as you'll shortly see, a variable is never expanded unless it is registered with the Perl script using the module, this is not a problem.
Case - outside of values - is never significant. The FUNCTION
may, from version 2.0b10, be one of either:
include
set
callback
... in Perl scripts
PetiteCGI::Template is an object-oriented module. As such each template
(physically a file) is
an object. The Perl syntax for creating
these is, of course:
my($templateRef) = new Template({}) ;
$templateRef
you can, of course, replace by something
of your own choice. The new()
method takes an associative
array - hash - as its only argument. This example uses the default
values to illustrate:
my($templateRef) = new Template({ 'sourceFile' => '', 'sourcePath' => $ENV{'PATH_TRANSLATED'}, 'outputPath' => \*STDOUT, 'expandEmptyItems' => 'no', 'matchDefaultSubstring' => 'no', 'debug' => 0, }) ;
where
sourceFile
- ... is the name of the file that is the template. If this file does
not start with a '/', then it is looked for in (a)
./sourceFile
and (b)sourcePath/sourceFile
as one would expect. sourcePath
- ... sets the path to the physical location where that file might be found IF the file does not start with a slash ('/').
outputPath
- ... is the filehandle where the template is written.
expandEmptyItems
- If this is set to
yes
, thenstartTag
andendTag
will be output even if the callback generates an empty ("") or undefined value. matchDefaultSubstring
- If set to
yes
, then the default value specified will match a generated value even if it is a substring of same. Ifno
, then only exact matches will generate aselectedItem
. debug
- ... takes a 0 or a 1, and controls the verbosity of the module.
Variables
Using variables is a well-known way to manage - or mangle - code. It is only fair that PetiteCGI::Template should have these contructs, and only natural that it does it in a non-common way.
Defined
Defining a variable in a template is done with the
template set
construct:
<!-- template set name = "uri" value = "http://www.greytower.net/" -->
When processing templates the PetiteCGI::Template module will gather up these variables, and store them.
Once a variable has been set, it can be retrieved in the Perl program by something along these lines:
my($uri) = $templateRef->getValue("uri") ;
Note that a variable set in one template will not become
available in another unless the former is include
'ed in the
latter. See
Content Inclusion
You could also define variables in the Perl code for later expansion in the template - this might be the most common method:
$templateRef->register({ 'name' => "uri", 'callback' => \"http://www.greytower.net/", 'callbackArguments' => [] }) ;
The register()
method takes three arguments: the variable
name, the data structure - typically a scalar - it is associated with,
and a list of arguments. The latter is used if the data structure is
actually a function:
$templateRef->register({ 'name' => "uri", 'callback' => \&getHostName, 'callbackArguments' => ["localhost"] }) ;
Expanded
Expanding a variable - either one defined in the template code itself or one defined in the Perl code - can be done in one of two ways. Either you'd use the callback syntax explained later:
<!-- template callback name = "calculatePi" startTag = "" endTag = "" item = "%%pi%%" -->
Or, since replacing a variable using the syntax above can be tedious, you could use the alternative which is exactly2 equivalent in functionality:
<p> The value of PI is approximately %%pi%% </p>
Callbacks
A callback in the world of PetiteCGI::Template is a Perl subroutine - though I persist in calling them functions - which is associated with a structure in the template. This allow you to construct more complex constructs.
A callback is defined with the syntax:
<!-- template callback name = "generateFruitList" startTag = "<ul>" endTag = "</ul>" item = "<li>%%fruitName%%>/li>" -->
Basic Operation
In short, the above will make PetiteCGI::Template call the routine
associated with the name generateFruitList
, use the return
values to create one, or more, copies of what is in item
,
wrap the whole thing in the content of startTag
and
endTag
, and replace the above with the result.
What you write in startTag
and endTag
is
your business - the template parser will not touch it in any way
what so ever. Naturally there are exceptions - namely that it
will expand variables and macros as one would expect.
You could, therefore, write:
<!-- template callback name = "generateFruitList" startTag = '<ol start="%%startIndex%%">' endTag = "</ol>" item = "<li>%%fruitName%%>/li>" -->
But since the start
attribute to OL
is
deprecated you wouldn't do that. Of course.
Return values
A function used as a callback can, as is normal with Perl, return absolutely anything. PetiteCGI::Template is however only able to grasp a limited set of anythings. There are the classic anythings:
- scalar
- The value returned will be used to expand the first template variable in an ITEM. If the scalar is a reference to another scalar, then it is de-referenced first. If the scalar is a reference to an array, or a hash, then it is de-referenced and treated as suggested below.
- array
- Each value in the list will be used to create one copy of the structure in ITEM, replacing the first template variable found. If the value is a reference to a scalar, then it will be de-referenced before use. If the value is a reference to an array or a hash, then it'll be de-referenced and treated as suggested below.
- hash
- Each key/value pair will produce one copy of the ITEM structure with the first variable replaced by the key, the second by the value.
... and then there are the rather more complex anythings that the module will - try to - recognise:
- array of array
- Each item in the list will produce one copy of the structure in the ITEM construct. Variables are replaced left to right with values from the nested lists.
- array of hash
- Each item in the list will produce one copy of the structure in the ITEM construct. Variables are replaced with the value associated with each named key.
At each point in the above where the value could be a reference, that reference will be de-referenced before use - this also applies if it points to a piece of code, anonymous or otherwise. Some caution is suggested in using this feature.
PetiteCGI::Template will not make any effort to avoid circular references or code refs calling other code refs reductio ad absurdum - it's a template library, not a miracle-worker (or a Doctor).
The treatment of return values allow you to do things like:
<table> <thead> <tr> <th>Name</th> <th>Age</th> </tr> </thead> <tbody> <!-- loop begins here --> <!-- template callback name="getAgeList" startTag="<tr>" endTag="</tr>" item="<td>%%name%%</td><td>%%age%%</td>" --> </tbody> </table>
Then, in the Perl code:
$templateRef->register({ 'name' => "getAgeList", 'callback' => \&getSortedAgeList, 'callbackArguments' => [] }) ;
If getSortedAgeList()
now return either an array of arrays
with exactly two entries in each 2nd level array:
[['Donald', 60], ['Daisy', 59] ]
or an array of hash with names chosen to match:
[{'name' => 'Donald', 'age' => '60'}, {'name' => 'Daisy', 'age' => '59'}]
... the above construct will produce:
<table> <thead> <tr> <th>Name</th> <th>Age</th> </tr> </thead> <tbody> <!-- loop begins here --> <tr> <td>Donald</td><td>60</td> </tr> <tr> <td>Daisy</td><td>50</td> </tr> </tbody> </table>
Content Inclusion
Odd as it might seem, the very first thing done by the template parser is to include external content. The reason for that becomes obvious when you think back to how variables are handled.
To include content from a truly external source - such as a file or an
URI - you can use the
include
construct:
<!-- template include src = "http://www.greytower.net/" -->
Inclusion defaults to file://
, and what was said about
paths in the description of
Perl syntax holds here as well.
It is worth your while to note what the above does with variables. This construct:
<!-- template set name = "uri" value = "http://www.greytower.net/" --> <!-- template include src = "%%uri%%" -->
... will not work. However, put the following in your Perl code prior to the template code being parsed:
$templateRef->register({ 'name' => "uri", 'callback' => \"http://www.greytower.net/", 'callbackArguments' => [] }) ;
And this in the template:
<!-- template include src = "%%uri%%" -->
... and all is well.
Default Values
Many people seem to think it's a good thing to present defaults to the
user - in particular through the use of selected
in the
HTML OPTION
element.
This does, however, present somewhat of a problem when the aim is to strictly separate template and code. It means that there need to be a syntax with which the template author can communicate to the Perl author that something should be marked as default. I also need to add code in the Perl module to ensure that PetiteCGI::Template understands this.
The first idea - trusting the template parser to understand where to put
a random-language specific phrase or word indicating default
- was
pure insanity. Luckily I surround myself with more or less sane people,
and Jörgen came up with the idea of redundancy:
<!-- template callback name = "generateFruitList" startTag = '<ol start="%%startIndex%%">' endTag = "</ol>" item = "<li>%%fruitName%%</li>" selectedItem = "<li><img src="default.png" alt="">%%fruitName%%</li>" -->
The idea, of course, is that the selectedItem
code should be
used to represent a defaulted item. The next example makes somewhat more
sense:
<!-- template callback name = "getFruitList" startTag = '<select name="fruit">' endTag = "</select>" item = '<option value="%%fruitName%%">%%fruitName%%</option>' selectedItem = '<option value="%%fruitName%%" selected>%%fruitName%%</option>' -->
Of course, we're still left with the Perl pieces. However, by adding yet
another argument to the register()
method:
$templateRef->register({ 'name' => "getFruitList", 'callback' => \&getSortedFruitList, 'callbackArguments' => [], 'default' => \"Apple", 'defaultKey' => '', 'defaultArguments' => [], }) ;
where
name
- ... is the name associating the callback with the template
callback
- ... is a reference to a data structure containing the data to be used in the replacement, or a reference to a routine/method that returns such a data structure
callbackArguments
- ... is an anonymous array listing arguments for a routine used as a callback
default
- ... is a reference to either a data structure or a routine that
represents the default value to be used with the
selectedItem
construct in a template defaultKey
- ... is a scalar variable representing the name of a field in which to check for the default values. This is typically used when the callback results in an array of references to hashes.
defaultArguments
- ... is an anonymous array listing arguments for a routine used as a default source.
If the callback produces a value which matches the default key, then the
selectedItem
code is used as the template. If it does not,
then item
is used.
1 Come to think of it, I don't think this would work very well with any type of binary code, but you're welcome to try. Any time. Any where. Just don't blaim me if your PNG files come out as neatly coded markup ...
2 Well. Almost.