An Introduction to Bricolage Templates

mikeraynham edited this page May 20, 2011 · 10 revisions
Clone this wiki locally

This document introduces Bricolage templates, and shows how they can be used to simplify designs and reduce code repetition. It assumes that the reader understands the concepts behind a web template engine. Template Toolkit code is used, but if you are familiar other template engines, such as Mason or HTML::Template, then this document should be simple enough to follow.

A rudimentary understanding of Bricolage elements is also required. If you’ve read Bric::Templates or Bric::AdvTemplates and not quite grasped it, then this document may help.

Examples in this document refer to the following single page of XHTML:

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" > 
    <head> 
        <title>Bricolage 2.0 Elements and Templates</title> 
    </head> 
    <body> 
 
        <h1>Bricolage 2.0 Elements and Templates</h1> 
 
        <div id="teaser"> 
            <h2>The only web publishing system that doesn't think it's smarter than you are.</h2> 
            <ul> 
                <li>No boilerplate themes to restrict your creativity</li> 
                <li>No assumptions about how you want your site to work</li> 
                <li>No wasted time figuring out "The Right Way" to do it</li> 
            </ul> 
            <p>Stop trying to hack your way out of someone else's paper bag, and start enjoying
               total freedom.</p> 
        </div> 
 
        <div id="pitch"> 
            <ul> 
                <li><strong>Built for serious hackers.</strong> Get the job done &hellip;</li> 
                <li><strong>A perfectionist's dream.</strong> Let your creativity flow &hellip;</li> 
                <li><strong>Makes teamwork a snap.</strong> Collaborate easily &hellip;</li> 
            </ul> 
            <blockquote> 
                <p>Bricolage is like a box of magic crayons.</p> 
                <p class="attribution">&mdash; Bret Dawson, Pectopah</p> 
            </blockquote> 
        </div> 
 
  </body> 
</html>

Story Elements

The above code was generated from a top-level Story element (called Story) in which there are two Container subelements (Teaser and Pitch), together with a minimal XHTML document structure. The subelement definitions are as follows:

Teaser

The Teaser subelement comprises:

  • a single strapline text box
  • a single bullet_list subelement
  • one or more paragraph text areas

Pitch

The Pitch subelement comprises:

  • a single bullet_list subelement
  • a single block_quote subelement

Bullet List

The Bullet List subelement comprises:

  • one or more bullet_point text boxes

Block Quote

The Block Quote subelement comprises:

  • one or more paragraph text areas
  • a single attribution text box

Templates

One way to create the above code is to create a single story template to handle the entire page, as shown in this /story.tt Template Toolkit example:

Template → New Template

Template Type Category Element
Element / story

/story.tt

[% teaser = element.get_elements('teaser')
   pitch  = element.get_elements('pitch')
~%]

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
    <head>
        <title>[% story.get_title() %]</title>
    </head>
    <body>

        <h1>[% story.get_title() %]</h1>

        <div id="teaser">
      [%~ FOREACH e IN teaser.get_elements() %]
        [%~ SWITCH e.get_key_name() %]

          [%~ CASE 'strapline' %]
            <h2>[% e.get_value() %]</h2>

          [%~ CASE 'bullet_list' %]
            <ul>
            [%~ FOREACH bp IN e.get_elements('bullet_point') %]
                <li>[% bp.get_value() %]</li>
            [%~ END %]
            </ul>

          [%~ CASE 'paragraph' %]
            <p>[% e.get_value() %]</p>

        [%~ END %]
      [%~ END %]
        </div>

        <div id="pitch">
      [%~ FOREACH e IN pitch.get_elements() %]
        [%~ SWITCH e.get_key_name() %]

          [%~ CASE 'bullet_list' %]
            <ul>
            [%~ FOREACH bp IN e.get_elements('bullet_point') %]
                <li>[% bp.get_value() %]</li>
            [%~ END %]
            </ul>

          [%~ CASE 'blockquote' %]
            <blockquote>
            [%~ FOREACH p IN e.get_elements('paragraph') %]
                <p>[% p.get_value() %]</p>
            [%~ END %]
                <p class="attribution">&mdash; [% e.get_value('attribution') %]</p>
            </blockquote>

        [%~ END %]
      [%~ END %]
        </div>

  </body>
</html>

Even with such a simple page, there are clearly some flaws to this approach:

  • The XHTML document structure (the doctype, head and body sections) are not relevant to the story — it would be cleaner if they were located in a separate template
  • The Teaser and Pitch elements might be used on other pages, so they too could be located in separate templates
  • Code for the Bullet List element is repeated twice — once for the Teaser and once for the Pitch
  • If different pages require different layouts, the XHTML document structure and any other common parts have to be repeated

Creating a Wrapper

Moving the XHTML document structure to a separate template will help keep the story templates cleaner and easier to maintain. Wrappers (or autohandlers in Mason), are wrapped around the story content. The story and subelement templates handle all the content — the stuff that changes from page to page, and the wrapper is then wrapped around this content.

To create a wrapper in Bricolage, create a Category Template:

Template → New Template

Template Type Category
Category /

Bricolage will automatically name the template based on its category and the output channel burner. If, as in this example, you are using Template Toolkit, it will be called wrapper.tt.

Our Template Toolkit wrapper for the above code looks like this:

/wrapper.tt

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
    <head>
        <title>[% story.get_title() %]</title>
    </head>
    <body>

        <h1>[% story.get_title() %]</h1>

        [% content %]

  </body>
</html>

The content variable is created automatically by Template Toolkit. It includes all the content that has been created by the story template.

Story Template — Revision 1

Now that the XHTML document structure has been moved to a category template, it can be removed from the Story template. Here’s the revised version of /story.tt:

/story.tt

[% teaser = element.get_elements('teaser')
   pitch  = element.get_elements('pitch')
~%]

  <div id="teaser">
[%~ FOREACH e IN teaser.get_elements() %]
  [%~ SWITCH e.get_key_name() %]

    [%~ CASE 'strapline' %]
      <h2>[% e.get_value() %]</h2>

    [%~ CASE 'bullet_list' %]
      <ul>
      [%~ FOREACH bp IN e.get_elements('bullet_point') %]
          <li>[% bp.get_value() %]</li>
      [%~ END %]
      </ul>

    [%~ CASE 'paragraph' %]
      <p>[% e.get_value() %]</p>

  [%~ END %]
[%~ END %]
  </div>

  <div id="pitch">
[%~ FOREACH e IN pitch.get_elements() %]
  [%~ SWITCH e.get_key_name() %]

    [%~ CASE 'bullet_list' %]
      <ul>
      [%~ FOREACH bp IN e.get_elements('bullet_point') %]
          <li>[% bp.get_value() %]</li>
      [%~ END %]
      </ul>

    [%~ CASE 'blockquote' %]
      <blockquote>
      [%~ FOREACH p IN e.get_elements('paragraph') %]
          <p>[% p.get_value() %]</p>
      [%~ END %]
          <p class="attribution">&mdash; [% e.get_value('attribution') %]</p>
      </blockquote>

  [%~ END %]
[%~ END %]
  </div>

That’s an improvement, but the code can still be split into more specific and re-usable chunks.

Bullet List

Let’s start by creating a simple template for the bullet_list element:

Template → New Template

Template Type Category Element
Element / bullet_list

Bricolage will automatically name the template based on its category and the output channel burner. If, as in this example, you are using Template Toolkit, it will be called bullet_list.tt.

We’ll put the Bullet List code into the new bullet_list.tt template:

<ul>
[%~ FOREACH bp IN element.get_elements('bullet_point') %]
    <li>[% bp.get_value() %]</li>
[%~ END %]
</ul>

Notice how the FOREACH line has been changed slightly. When the code was in /story.tt, the outer FOREACH loop set up a variable called e which contained the element to be processed. /story.tt now passes e to bullet_list.tt via the burner’s display_element method, as shown below. bullet_list.tt receives this in the element variable.

Story Template — Revision 2

/story.tt

[% teaser = element.get_elements('teaser')
   pitch  = element.get_elements('pitch')
~%]

  <div id="teaser">
[%~ FOREACH e IN teaser.get_elements() %]
  [%~ SWITCH e.get_key_name() %]

    [%~ CASE 'strapline' %]
      <h2>[% e.get_value() %]</h2>

    [%~ CASE 'bullet_list' %]
      [% burner.display_element(e) %]

    [%~ CASE 'paragraph' %]
      <p>[% e.get_value() %]</p>

  [%~ END %]
[%~ END %]
  </div>

  <div id="pitch">
[%~ FOREACH e IN pitch.get_elements() %]
  [%~ SWITCH e.get_key_name() %]

    [%~ CASE 'bullet_list' %]
      [% burner.display_element(e) %]

    [%~ CASE 'blockquote' %]
      <blockquote>
      [%~ FOREACH p IN e.get_elements('paragraph') %]
          <p>[% p.get_value() %]</p>
      [%~ END %]
          <p class="attribution">&mdash; [% e.get_value('attribution') %]</p>
      </blockquote>

  [%~ END %]
[%~ END %]
  </div>

The duplicate code to create bullet lists has now been factored out into its own template. /story.tt uses the burner.display_element($element) method to pass e to the relevant template. Bricolage will search the template hierarchy for a template that matches the key name of the element passed to burner.display_element($element) — in this case, it searches for bullet_list.tt (the template .tt extension is derived from the output channel burner).

Let’s do the same thing for blockquote, teaser and pitch:

Blockquote

Template → New Template

Template Type Category Element
Element / blockquote

/blockquote.tt

<blockquote>
[%~ FOREACH p IN element.get_elements('paragraph') %]
    <p>[% p.get_value() %]</p>
[%~ END %]
    <p class="attribution">&mdash; [% element.get_value('attribution') %]</p>
</blockquote>

Teaser

Template → New Template

Template Type Category Element
Element / teaser

/teaser.tt

<div id="teaser">
[%~ FOREACH e IN element.get_elements() %]
  [%~ SWITCH e.get_key_name() %]

    [%~ CASE 'strapline' %]
      <h2>[% e.get_value() %]</h2>

    [%~ CASE 'bullet_list' %]
      [% burner.display_element(e) %]

    [%~ CASE 'paragraph' %]
      <p>[% e.get_value() %]</p>

  [%~ END %]
[%~ END %]
</div>

Pitch

Template → New Template

Template Type Category Element
Element / pitch

/pitch.tt

<div id="pitch">
[%~ FOREACH e IN element.get_elements() %]
  [%~ SWITCH e.get_key_name() %]

    [%~ CASE 'bullet_list' %]
      [% burner.display_element(e) %]

    [%~ CASE 'blockquote' %]
      [% burner.display_element(e) %]

  [%~ END %]
[%~ END %]
</div>

Bricolage will automatically find the correct template based on the element type (the type of object in e), so the SWITCH statement in the above code is now redundant. As the template simply delegates to other templates, we can remove the SWITCH statement altogether:

/pitch.tt

<div id="pitch">
[%~ FOREACH e IN element.get_elements() %]
  [% burner.display_element(e) %]
[%~ END %]
</div>

Story Template — Revision 3

/story.tt

[% teaser = element.get_elements('teaser')
   pitch  = element.get_elements('pitch')
~%]

[% burner.display_element(teaser) %]
[% burner.display_element(pitch) %]

/story.tt has now been whittled right down, but we can make it smaller and more generic by removing references to teaser and pitch altogether:

Story Template — Revision 4

/story.tt

[%~ FOREACH e IN element.get_elements() %]
  [% burner.display_element(e) %]
[%~ END %]

Summary

Breaking down pages into their logical components makes it simpler to maintain and re-use code. As the above example shows, even a simple page can benefit from this approach. At each stage in this process, the XHTML output has remained the same, but the code that creates it has become cleaner, easier to maintain and more reusable.

These simple approaches can be combined with nested category templates and element template overrides to great effect.