Bric::AdvTemplates(3p)

Name
Bric::AdvTemplates - Template Producing: Advanced Topics

Version
$LastChangedRevision$

Date
$LastChangedDate: 2005-12-19 21:39:08 +0100 (Mon, 19 Dec 2005) $

Introduction
This document discusses some templating techniques that require existing knowledge of the Bricolage publishing system. The text and examples assume that you have already read and understood the Bric::Templates.

Utility Templates
Bricolage supports three different types of templates. We've already discussed category templates and element templates in Bric::Templates. Here we introduce the third: utility templates.

Utility templates are associated with no element types, unlike element templates. They are associated with categories, but only to manage where they're stored on the file system; they of course do not execute as part of a category template chain.

The purpose of utility templates is to create libraries of commonly reused code, so that you can maintain that code in one place, rather than copy and paste it into many different element and/or category templates. Most Bricolage templaters create a category structure just for utility templates. For example, you might have a category called /util, with a /util/xhtml subcategory of templates specific to XHTML output.

Utility templates are never executed by Bricolage. Rather, you execute them using the standard calling syntax of your templating architecture. For example, say that you want to have a simple include to pull in at the end of a story, and you support many different types of stories. The simple thing to do is to create a utility template. In Mason, I might create a utility template named /util/xhtml/license.mc that looks like this:

  <%args>
  $license
  </%args>
  <div>Licensed under the <% $license %> license</div>\

To call it, I just use native Mason syntax at the bottom of a story template:

  <& /util/xhtml/license.mc, license => 'Creative Commons' &>

In Template Toolkit, the utility template would be named /util/xhtml/license.tt and looks like this:

  <div>Licensed under the [% license %] license</div>

To call it I just use native TT syntax at the bottom of a story template:

  [% INCLUDE util/xhtml/license.tt license = 'BSD' -%]

In PHP, the utility template would be named /util/xhtml/license.php and looks like this:

  <?php
  function license($license = "GPL") {
      echo "<div>Licensed under the $license license</div>\n";
  }
  ?>

As you can see, it's a bit different in PHP, as the file doesn't actually output anything itself, but declares a function that does. We just need to include the template and then call the function:

  <?php
     include_once("util/xhtml/license.php");
     license('BSD');
  ?>

In HTML::Template templates, it's not possible to pass arguments to an included .tmpl template; use a .pl template to define functions, if necessary. But you can of course include another template. So if the utility template, named /util/xhtml/license.tmpl, looked something like this:

  <tmpl_if include_license>
    <div>Licensed under the Creative Commons license</div>
  </tmpl_if>

You can include it like so:

  <tmpl_include name="util/xhtml/license.tmpl">

Of course these are contrived examples. But hopefully the utility (no pun intended) of utility templates is apparent.

Multipage Stories
As discussed in Bric::Templates, element types in Bricolage can be marked as "paginated." Such element types can be used to re-execute their element templates over successive pages, automatically outputting a new file each time. All templates output to a file named for the "File Name" and "File Extension" properties of the current Output Channel. Templates based on paginated element types will simply append a number to the output file for each additional time they run.

For example, a story containing four paginated elements will (assuming that the "File Name" and "File Extension" properties are set to "index" and "html", respectively), upon publication, produce the files:

index.html
index1.html
index2.html
index3.html

Sometimes you'd rather not use the numbers, but some other identifier for successive page file names. In such a case, you can use the burner object's "set_page_extensions()" method to set alternatives. If there are more pages than extensions, the burner will start using numbers again. For example, this statement:

  $burner->set_page_extensions('', '_two', '_three');

Would cause a four-page story to output the following file names;

index.html
index_two.html
index_three.html
index1.html

Now, regardless of the file names, there are two ways to execute templates based on paginated elements. The first is the normal "display_element()" method, and the second is the "display_pages()" method.

  Using "display_element()"
When formatting a paginated element using "display_element()", the page elements will not trigger the output of data to a new file. Thus "display_element()" handles paginated elements in the same way that it handles all other elements. Use this approach to output all pages on a single page, such as for the print version of a story.

For example, say we have a "Column" story that contains paginated "Page" subelements. Here is some Mason template code for "Page" (assume that it contains only paragraphs):

      <!-- page -->
  % foreach my $p ($element->get_elements('paragraph')) {
      <p><% $p->get_value %></p>
  % }

In Template Toolkit, that's:

      <!-- page -->
  [% FOREACH e = element.get_elements('paragraph') -%]
      <p>[% e.get_value %]</p>
  [% END -%]

And in PHP, it's:

      <!-- page -->
  <?php
  foreach ($element->get_elements('paragraph') as $e) {
      echo '    <p>', $e->get_value(), "</p>\n";
  }
  ?>

And now, some simple template code for "Column." Mason first:

    <html>
      <head><title><% $story->get_title %></title></head>
      <body>
    % $burner->display_element($_) for $element->get_elements('page');
      </body>
    </html>

Next, Template Toolkit:

    <html>
      <head><title>[% story.get_title() %]</title></head>
      <body>
  [% FOREACH e = element.get_elements('page') -%]
      [% burner.display_element(e) %]
  [% END -%]
      </body>
    </html>

And finally, of course, PHP:

    <html>
      <head><title><?= $story->get_title() ?></title></head>
      <body>
        <?php
        foreach ($element->get_elements('page') as $e) {
            $burner->display_element($e);
        }
        ?>
      </body>
    </html>

Now, 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. For a column story with three "Page" subelements, the output would look something like this:

  <html>
    <head><title>Our Column Title</title></head>
    <body>

      <!-- page -->
      <p>page 1 para 1 content</p>
      <p>page 1 para 2 content</p>

      <!-- page -->
      <p>page 2 para 1 content</p>
      <p>page 2 para 2 content</p>

      <!-- page -->
      <p>page 3 para 1 content</p>
      <p>page 3 para 2 content</p>

    </body>
  </html>

  Using "display_pages()"
The "display_pages()" method, on the other hand, creates new files for each page element. The "display_pages()" method takes a single key name or an array reference of key names of paginated elements:

  % my @page_key_names = qw/page another_page/;
  % $burner->display_pages(\@page_key_names);

Here, pages with key names "page" and "another_page" are published. The template with the call to "display_pages()", as well as any surrounding category templates, is run multiple times, once for every instance of a paginated element identified by the key names argument. So if we have a "Column" story containing three "Page" subelements and no "Another Page" elements, the "Column" template will be executed three times. Each time it runs, the "display_pages()" method will execute the "Page" template with each succeeding page.

So we can use our earlier example with just a minor change to the Column template to produce the output we want. The Mason template is now:

    <html>
      <head><title><% $story->get_title %></title></head>
      <body>
    % $burner->display_pages([ qw(page another_page) ]);
      </body>
    </html>

Template Toolkit now looks like this:

    <html>
      <head><title>[% story.get_title() %]</title></head>
      <body>
  [% burner.display_pages([ 'page', 'another_page' ]) %]
      </body>
    </html>

And PHP looks like this:

    <html>
      <head><title><?= $story->get_title() ?></title></head>
      <body>
        <?php
    $burner->display_pages(array( 'page', 'another_page' ));
        ?>
      </body>
    </html>

Note that in each case, we've removed the "foreach" block with its call to "display_element()" and substituted a call to "display_pages()". The output is now in three files that look like this:

index.html:

  <html>
    <head><title>Our Column Title</title></head>
    <body>

      <!-- page -->
      <p>page 1 para 1 content</p>
      <p>page 1 para 2 content</p>

    </body>
  </html>

index1.html:

  <html>
    <head><title>Our Column Title</title></head>
    <body>

      <!-- page -->
      <p>page 2 para 1 content</p>
      <p>page 2 para 2 content</p>

    </body>
  </html>

index2.html:

  <html>
    <head><title>Our Column Title</title></head>
    <body>

      <!-- page -->
      <p>page 3 para 1 content</p>
      <p>page 3 para 2 content</p>

    </body>
  </html>

Including Related Stories and Media
Certain types of container elements can have other stories or media objects related to them. If an element has a related story or media document (or both!), you can get at it using methods on the container element. In Mason:

    % my $rel_story = $element->get_related_story;
    % my $rel_media = $element->get_related_media;

In Template Toolkit:

    [% rel_story = element.get_related_story %]
    [% rel_media = element.get_related_media %]

In PHP:

    $rel_story = $element->get_related_story();
    $rel_media = $element->get_related_media();

The related story object is of course in the same class as the global $story object. To get the top element of the related story, call the "get_element()" method. The returned element is in the same class as the global $element object: It's the top-level container element for the related story.

Related media are Bric::Biz::Asset::Business::Media objects. They inherit from the same class as the story class, namely Bric::Biz::Asset::Busines, but has a slightly different interface. These methods are the most notable:

"get_uri($oc)"
    Media objects are associated with only a single category, and so there is no distinction between a primary URI and secondary URIs. But thanks to their ability to be associated multiple output channels, they might have different output channel formats. You can therefore pass in an output channel object or ID to get the media document's URI for that output channel?

    As of Bricolage 1.10, there is also a "primary_uri()" method in the media class to create parity with the interface of the story class. But, really, for creating URIs in Templates, you're best off using the "best_uri()" method of the burner class:

      my $uri = $burner->best_uri($rel_media)->as_string;

"get_media_type()"
    Returns a <LBric::Util::MediaType|Bric::Util::MediaType> object describing the media type (a.k.a. the "MIME Type") of the media document.

Bricolage Mason Custom Tags
The Bricolage Mason burner extends HTML::Mason tags so that template developers can write blocks of code that only run in certain contexts.

There are four contexts in which a template is executed, and each context has a corresponding Mason tag:

"<%chk_syntax>"
    Only runs in syntax checking--that is, when saving a template in the Templating UI. This tag is useful to comment out code that you want to check as valid Perl code, but not to be used in any other context.

      <%chk_syntax>;
      print "This is a no-op.\n";
      </%chk_syntax>

"<%preview>"
    Code inside a "<%preview>" tag executes only during previews, and is ignored by publishes. It can be useful for sending messages to the previewer or carrying out some action that should only be carried out during previews:

      <%preview>;
      $burner->throw_error('What are you thinking??')
          if $element->get_value('is_porn');
      </%preview>

"<%publish>"
    Code inside a "<%publish>" block will only execute during a publish, and not during a preview. For example, you might want to create a record in a database only when a story is published:

      <%publish>;
      my $dbh = DBI->connect('DBI:mysql:database=mydb;host=db.example.com');
      $dbh->do(
          'INSERT INTO blog VALUES (?, ?)',
          undef,
          $story->get_title,
          '<p>' . join(
              '</p><p>',
               map { $_->get_value } $element->get_elements('paragraph')
          ) . '</p>'
      );
      </%publish>

"<%realtime>"
    This block is equivalent to Mason's "<%text>" block. Any of its contents will be ignored by the burner and simply output to the file. Once distributed to a Mason-powered destination server, any Mason code inside the block will execute:

      <%realtime>
      <& /path/to/delivery_server/include.mhtml &>
      </%realtime>

Formatting XML in the Mason burner
In order to simplify the Mason template developer's life to some degree, we've created bricolage.conf configuration directives that, when enabled, will add a global variable to the Mason name space called $writer. This variable contains an XML::Writer object that can be used to output XML from your templates. Of course you can simply use Mason for outputting XML, but if you need to output a lot of XML, you might want to take advantage of the XML::Writer object.

To use $writer, simply set the "INCLUDE_XML_WRITER" bricolage.conf directive to "Yes". Another directive, "XML_WRITER_ARGS", can be used to pass extra parameters to the XML::Writer constructor (see XML::Writer for details of the arguments it accepts -- just don't use the "OUTPUT" parameter, 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>" block. Here's an example:

  <%perl>;
  $writer->startTag('greeting', 'class' => 'simple');
  $writer->characters('Hello, world!');
  $writer->endTag('greeting');
  $writer->end;
  </%perl>

See the XML::Writer documentation for more details on how to use it.

Using Other Perl Modules
Since Bricolage's templates are all Perl-based (even the PHP templates run inside a PHP interpreter inside a Perl interpreter), it is of course possible to load Perl modules and use them in your templates. You might want to use the 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 strengths of Bricolage's templating architecture, and we strongly urge you to exploit it.

However, it's not efficient to load modules directly in your templates, since every time they're run, they'll load the template into a separate Apache process, and therefore use more system resources (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 therefore use less memory.

With the "PERL_LOADER" bricolage.conf configuration directive, 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 name space reserved for Mason, Template Toolkit, and PHP templates (any exports won't be available to HTML::Template templates, although any imported modules of course will be). 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;

Author
Garth Webb <garth@perijove.com>

David Wheeler <david@kineticode.com>

See Also

Bric::Templates
Bric::Biz::ElementType
Bric::Biz::Asset::Template
Bric::Biz::Asset::Business::Story
Bric::Biz::Element
Bric::Biz::Element::Field
Bric::Biz::Element::Container
Bric::Util::Burner
Bric::Util::Burner::Mason
Bric::Util::Burner::TemplateToolkit
Bric::Util::Burner::Template
Bric::Util::Burner::PHP