Bric::AdvTemplates - Template Producing: Advanced Topics
$Revision: 1.9.2.1 $
$Date: 2002/02/26 01:31:08 $
This document discusses some templating techniques that require existing knowledge of the Bricolage publishing system. The text and examples will assume the reader has already read and understood the first template document, 'Producing Templates on the Bricolage System'.
The topics discussed in this document will be:
Autohandler Templates
Multi-page stories
Multi-page stories using autohandlers
Including related stories and media
It is possible not to associate a template with an Element. Doing so creates a template that will be used for every type of Story in a particular category and output channel. These are called "Autohandler Templates" or just "Autohandlers".
Autohandlers usually provide wrapper HTML for normal Templates, such as a standard site header and footer. Unlike normal templates that display particular fields of a story, autohandlers will usually just have some HTML along with a line of code that says "put all remaining output here". This line of code is the 'chain_next' method of the $burner object passed into all tempaltes. It is called like this:
% $burner->chain_next;
Autohandlers do have access to the $story and $element objects as well. You might use these if you want to add some customization based on the story.
Autohandlers run before any normal template is run, and if there is more than one autohandler in a diretory tree, they will each run in order from the top of the directory tree down.
For example, lets say we publish a Column story in the News/Polictics category and there is a Root category autohandler and a News category autohandler:
/News/Politics/column.mc
/autohandler
/News/autohandler
When this Story is published, the system searches for any autohandlers, starting at '/' working its way down to the publish category '/News/Politics'. In this example, the tempates will run in the following order:
/autohandler
/News/autohandler
/News/Politics/column.mc
Here is some very simple code to illustrate the above publish. Here is the code for the root level autohandler:
<!-- Code for /autohandler -->
<html>
<head><title>The Site</title></head>
<body bgcolor='white'>
% $burner->chain_next;
</body>
</html>
Here is some code for the 'News' category autohandler:
<!-- Code for /News/autohandler -->
<h1>Weekly News</h1>
<table><tr><td width=570>
% $burner->chain_next;
</td></tr></table>
Here is some code for the 'Column' template:
<!-- Code for /News/Politics/column.mc -->
<b><% $story->get_title %></b>
% my $n = 1;
% while (my $p = $element->get_data('paragraph', $n++)) {
<p><% $p %></p>
% }
Now, if we publish a Column story that has a title 'Man Bites Dog' and contains two paragraphs of text, the resulting published story would look like:
<!-- Code for /autohandler -->
<html>
<head><title>The Site</title></head>
<body bgcolor='white'>
<!-- Code for /News/autohandler -->
<h1>Weekly News</h1>
<table><tr><td width=570>
<!-- Code for /News/Politics/column.mc -->
<b><% $story->get_title %></b>
<p>Earlier today a man was reported biting a dog</p>
<p>The dog was unavailble for comment</p>
</td></tr></table>
</body>
</html>
Elements in the Bricolage system can be marked as "paginated". Such Elements can be used to re-execute templates over successive pages, automatically outputting a new file each time. All templates output to a file named for the "File Name" and "File Extention" properties of the current Output Channel. Templates based on paginated Elements will simply append a number to the output file for each additional time they run. So a story containing 4 paginated elements will (assuming that the "File Name" and "File Extension" properties are set to "index" and "html", respectively), upon publication, produce the files:
There are two ways to display templates based on paginated elements. The first is the normal 'display_element' method that is used to display all elements. The second is by using the 'display_pages' method.
When displaying a paginated element using 'display_element', the output of the page elements will not trigger output of the data to a new file. Thus display_element() handles paginated Elements in the same way it handled all other Elements. Use this approach to output all pages on a single page -- in print versions of a story, for example.
For example, say we have a 'Column' story that contains 'Page' elements. These 'Page' elements are paginated elements. Here is some template code for 'Page' (assume page contains just paragraphs):
<!-- Code for 'page.mc' -->
% my $n = 1;
% while (my $p = $element->get_data('paragraph', $n++)) {
<p><% $p %></p>
% }
Here is some template code for 'column':
<!-- Code for 'column' -->
<html>
<head><title><% $story->get_title %></title></head>
<body>
% may $n = 1;
% while (my $pg = $element->get_container('page', $n++)) {
% $burner->display_element($pg);
% }
</body>
</html>
These templates will work exactly as you would expect: each page is output as any other element is output, and the results of all this output are included in a single page:
index.html :
<!-- Code for 'column' -->
<html>
<head><title>Our Column Title</title></head>
<body>
<!-- Code for 'page.mc' -->
<p>page 1 para 1 content</p>
<p>page 1 para 2 content</p>
<!-- Code for 'page.mc' -->
<p>page 2 para 1 content</p>
<p>page 2 para 2 content</p>
<!-- Code for 'page.mc' -->
<p>page 3 para 1 content</p>
<p>page 3 para 2 content</p>
</body>
</html>
Use the 'display_pages' method to create new files for each page element. The 'display_pages' method takes the name of a paginated element:
% $burner->display_pages('page');
When a story is published, the entire template containing the call to 'display_pages' is run once for every instance of the named element passed to 'display_pages'. So if we have a 'Column' containing 3 'Page' elements, the 'Column' template will be run 3 times. Each time it runs, the 'display_element' call will run the 'Page' template with each succeeding page.
So we can use our earlier example with just a minor change to produce the output we want:
<!-- Code for 'column' -->
<html>
<head><title><% $story->get_title %></title></head>
<body>
% $burner->display_pages('page');
</body>
</html>
The code for the 'page' template:
<!-- Code for 'page.mc' -->
% my $n = 1;
% while (my $p = $element->get_data('paragraph', $n++)) {
<p><% $p %></p>
% }
This approach will output the following files:
index.html:
<!-- Code for 'column' -->
<html>
<head><title>Our Column Title</title></head>
<body>
<!-- Code for 'page.mc' -->
<p>page 1 para 1 content</p>
<p>page 1 para 2 content</p>
</body>
</html>
index1.html:
<!-- Code for 'column' -->
<html>
<head><title>Our Column Title</title></head>
<body>
<!-- Code for 'page.mc' -->
<p>page 2 para 1 content</p>
<p>page 2 para 2 content</p>
</body>
</html>
index2.html:
<!-- Code for 'column' -->
<html>
<head><title>Our Column Title</title></head>
<body>
<!-- Code for 'page.mc' -->
<p>page 3 para 1 content</p>
<p>page 3 para 2 content</p>
</body>
</html>
Autohandlers will work with multi-page templates using display_pages.
Certain types of elements can have other stories or media objects related to them. If an element has related stories or media you can get those objects using the methods:
% my $rel_story = $element->get_related_story;
% my $rel_media = $element->get_related_media;
The $rel_story object will have all the methods that the exported $story object has since they are the same object. To get the top element of the story you can use the method 'get_tile':
% my $rel_element = $rel_story->get_tile;
The $rel_element object is the same object as the exported $element object and has all the same methods.
The $rel_media object has the same methods as $story does but adds a few more specific to media objects. They are:
% my $uri = $rel_media->get_uri;
% my $type = $rel_media->get_media_type;
% my $size = $rel_media->get_size;
The method 'get_uri' returns the URI of the published media object. This can be used like:
<img src="<% $rel_media->get_uri %>">
in your templates. The 'get_media_type' method returns the mime type of the media object. These are text values of the form 'image/jpeg'.
The 'get_size' method will return the file size of the media file.
In order to simplify the template developer's life to some degree, we've created configuration directives that, when turned on, will add a global variable to all templates called $writer. This variable contains an XML::Writer object that can be used to output XML from your templates. Of course one can simply use Mason for outputting XML, but if one needs to output a lot of XML, one may wish to take advantage of the convenience interface priveded by XML::Writer.
To use $writer, simply set the INCLUDE_XML_WRITER configuration to "Yes" in bricolage.conf. Another directive, XML_WRITER_ARGS, can be used to pass extra arguments to the XML::Writer instantiator (see XML::Writer for details of the arguments it accepts -- just don't use the OUTPUT argument, as Bricolage needs to set this argument in order to ensure that all XML is properly output). Then all you need do is use $writer inside a %perl section. Here's an example:
<%perl>
$writer->startTag("greeting",
"class" => "simple");
$writer->characters("Hello, world!");
$writer->endTag("greeting");
$writer->end();
</%perl>
For more details on how to use XML::Writer, see XML::Writer.
Since Bricolage's templates are all Perl-based, it's of course possible to load Perl modules and use them in your templates. You might want to use DBI to pull data in from another database, or use XML::RSS to burn in headlines from another site. This is one of the great strenghts of Bricolage's templating architecture, and we strongly urge you to exploit this strength.
However, it's not efficient to load modules directly in your templates, since every time they're run, the'll load the template into a separate Apache process, and therefor use more system resouces (memory). It would be better to load all of the modules you'll need at Bricolage startup time, so that they get shared across processes and therefor use less memory.
With the PERL_LOADER configuration directive in bricolage.conf, you can do just that. The PERL_LOADER directive takes, on a single line, an arbitrary line of Perl code, and executes it in the namespace reserved for Mason templates (sorry, this functionality isn't availabe for HTML::Template templates at this time). So you can execute a whole bunch of Perl use statements here, and all the modules will be available to you in your templates without needing to reload them there. Here's an example:
PERL_LOADER = use XML::RSS; use CGI qw(:standard); use Apache::DBI;
Garth Webb <garth@perijove.com>
Bric, Bric::AdvTemplates, Bric::Biz::AssetType, Bric::Biz::Asset::Formatting, Bric::Biz::Asset::Business::Story, Bric::Biz::Asset::Business::Parts::Tile, Bric::Biz::Asset::Business::Parts::Tile::Data, Bric::Biz::Asset::Business::Parts::Tile::Container, Bric::Util::Burner