-
Notifications
You must be signed in to change notification settings - Fork 51
An Introduction to Bricolage Templates
This document introduces Bricolage templates, and shows how they can be used to simplify designs and reduce code repetition. 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 …</li>
<li><strong>A perfectionist's dream.</strong> Let your creativity flow …</li>
<li><strong>Makes teamwork a snap.</strong> Collaborate easily …</li>
</ul>
<blockquote>
<p>Bricolage is like a box of magic crayons.</p>
<p class="attribution">— Bret Dawson, Pectopah</p>
</blockquote>
</div>
</body>
</html>
The above code was generated from a top-level Story element (called Story) in which are two Container subelements (Teaser and Pitch), together with a minimal XHTML document template. The subelement definitions are as follows:
The Teaser subelement comprises:
- a single
strapline
text box - a single
bullet_list
subelement - one or more
paragraph
text areas
The Pitch subelement comprises:
- a single
bullet_list
subelement - a single
block_quote
subelement
The Bullet List subelement comprises:
- one or more
bullet_point
text boxes
The Block Quote subelement comprises:
- one or more
paragraph
text areas - a single
attribution
text box
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:
/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">— [% 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 re-coded
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.
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">— [% 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.
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.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">— [% 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
:
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">— [% element.get_value('attribution') %]</p>
</blockquote>
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>
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.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.tt
[%~ FOREACH e IN element.get_elements() %]
[% burner.display_element(e) %]
[%~ END %]
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. These simple approaches can be combined with nested category templates and element template overrides to great effect.