----------------------------------------
Writing HTML::Templates in Krang - A guide to using the Krang templating system.
This document describes how to use templating system in Krang, which is based on HTML::Template. An understanding of the way stories are constructed in Krang will be helpful in getting the most out of this document. A general understanding of how the HTML::Template templating language works will help as well.
Krang uses templates to generate output for stories when they're published. These templates are organized on the filesystem in the same category tree that is used for arranging stories and media. When a story is published, the category tree is searched for appropriate templates, starting at the story's current category. If no match is found immediately, Krang will then proceed up the category tree until a matching template is found.
Krang uses the HTML::Template templating system. The philosophy behind HTML::Template is to separate the code required to build the content of a page from the template used for the layout of the final page. Krang takes this philosophy one step further - by using the element tree of a story to automate the construction of a story's content, template developers can do their work with minimal programmer involvement.
If you are migrating from Bricolage, the templating system in Krang does not make use of separate .pl templates, the way that Bricolage did. In Krang, every element in the element tree contains code to publish itself. If customization is required, you can override the default publish methods within the element.
For further information on Krang's element tree, see Creating Element Libraries in Krang.
When first building templates for Krang, try and use the stock publish methods with one or more templates (See Publishing With Stock Krang Publish Methods, below). As you get more familiar with Krang, you will discover that using additional templates will allow you to customize the appearance of various sections of your site, without doing anything more than template development.
The publish methods that exist in Krang were designed with flexibility and performance in mind. Consider overriding methods (Publishing With Customized Publish Methods) in the publish process only in the event that you cannot get the behavior you need out of the standard methods.
Realize that overriding publish methods can potentially affect the performance of the preview/publish process, as any given method may be called a large number of times.
The publish methods provided by Krang should be sufficient for just about everyone. Get familiar with the standard publish process and use it for a little while before getting into the more advanced techniques in the publish process - chances are you won't need to use them.
The most important things to understand when getting into template development in Krang are the relationships that elements have to eachother (the element tree), and how those relationships are used to build out the data structures used in your templates.
We'll examine an example story type called Story. Here's the element tree for Story:
Story
- Deck (subclass of Krang::ElementClass::Text)
+ Page (subclass of Krang::ElementClass)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Pull Quote (subclass of Krang::ElementClass::Text)
- Paragraph (subclass of Krang::ElementClass::TextArea)
The Story element has a child element called Deck, and can contain any number of Page elements. Pages are composed of Paragraph and Pull Quote child elements, both of which can be repeated.
When the publish process is called on this story, Krang will walk this element tree, starting from the top (Story), recursively touching every element in the tree to construct the final output.
Each element within Story contains methods to retrieve the appropriate template, build the appropriate data to populate that template, and return the output that results from the merger of the data and template.
No element can return until its children have returned. Using the above tree as an example, a Page element will not return its output until its children, the Paragraph and Pull Quote elements, have returned their output.
Krang uses an internal method, Krang::ElementClass->fill_template() (see more in Publishing With Customized Publish Methods) to build the data available to a template. fill_template() will fill in the variables and loops in your template in a predictable fashion.
Several types of variables and loops are created. Using the element tree for Story above as an example, the following variables will be created:
An example of an immediate child of Story would be <tmpl_var deck>. An example of an immediate child of Page would be <tmpl_var pull_quote>.
A loop for each of these children, using its name followed by _loop. (e.g. <tmpl_loop page_loop>). The iterations of the loop are built one of two ways, depending on the variables referenced in the template:If the inside of the loop contains a direct reference to the child - e.g. <tmpl_var page> - and the child is either a primitive element (e.g. text field)
or a complex element for which a separate template exists (e.g. page.tmpl), then each iteration of the loop will contain $childname = HTML, where $childname is the name of the tmpl_var, and HTML
is the result of publishing $child. If not, each iteration will contain the variables returned by $child->fill_template() (i.e. the child's OWN children - <tmpl_var paragraph> and <tmpl_var pullquote> - will be populated).
Either way, each iteration will also contain the variable $childname_count (e.g. page_count)
is_$childname. In our example, <tmpl_loop element_loop> would contain <tmpl_var is_deck>, <tmpl_var deck>,
<tmpl_var is_page>, <tmpl_var page>.
(Note: If the template contains multiple instances of the same loop, each will
be populated with identical variables. This means that if ANY of them contains a
direct reference to the child, they will all have access to $childname = HTML,
and none will have access to the child's own children.)
e.g. <tmpl_var deck_total> would return 1, as would <tmpl_var page_total>.
A variable calledtitle containing $story->title.
This is available to all templates, regardless of where in the element tree you are.
Accessed in the template as <tmpl_var title>.
A variable calledslug containing $story->slug.
This is available to all templates, regardless of where in the element tree you are.
Accessed in the template as <tmpl_var slug>.
A variable calledpage_break that forces Krang to create a new page.
This is available to all templates. Careful as to where this is implemented, as you could find yourself creating far more pages than you intend to.
<tmpl_var page_break>
A loop calledcontrib_loop containing all contributors to a story.
See the section Contributors, further on in this document, for more information on Contributors.
pagination_loop containing links to other pages in the story.
See the section Using Pagination, further on in this document, for more information on Pagination. This variable is only available to elements marked as pageable.
With simple story types, it is easy to use a single template to handle the entire publish process.
For the purposes of this example, imagine the following element tree for a story:
Story
- Deck (subclass of Krang::ElementClass::Text)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Pull Quote (subclass of Krang::ElementClass::Text)
- Paragraph (subclass of Krang::ElementClass::TextArea)
The Template - Story.tmpl
<html>
<head>
<title><tmpl_var title></title>
</head>
<body>
<h1><tmpl_var title></h1>
<b><tmpl_var deck></b>
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<p><blockquote><i>
<tmpl_var pull_quote>
</i></blockquote></p>
</tmpl_if>
</tmpl_loop>
</body> </html>
This is a pretty boring example, but it illustrates the basic concepts of variable construction in Krang. <tmpl_loop element_loop> provides access to all child elements. <tmpl_loop paragraph_loop> and <tmpl_loop pull_quote_loop> are available, but are really not of much use. While <tmpl_var paragraph> exists only once in the template, because it is within the context of <tmpl_loop element_loop>, both paragraphs will be built.
Now let's imagine a more complex story type.
Story
- Deck (subclass of Krang::ElementClass::Text)
+ Page (subclass of Krang::ElementClass)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Pull Quote (subclass of Krang::ElementClass::Text)
- Paragraph (subclass of Krang::ElementClass::TextArea)
While this may not look like a big step up from the element tree in Example 1, we're now making use of an important concept in Krang - elements that contain other elements as their children.
The Template - Story.tmpl
<tmpl_loop page_loop>
<html>
<head>
<title><tmpl_var title></title>
</head>
<body>
<tmpl_if __first__>
<h1><tmpl_var title></h1>
<b><tmpl_var deck></b>
</tmpl_if>
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<p><blockquote><i>
<tmpl_var pull_quote>
</i></blockquote></p>
</tmpl_if>
</tmpl_loop>
Page Number: <tmpl_var page_count>
</body> </html>
<tmpl_unless __last__>
<tmpl_var page_break>
</tmpl_unless>
</tmpl_loop>
Results:
The Templates
If you prefer a modular approach, the same result can be achieved with multiple (smaller) templates. In our example above, this would mean creating a separate template for the Page element.
Page.tmpl
Page.tmpl will be identical to the <tmpl_loop element_loop> above. By removing it from Story.tmpl, we now have a small, easy-to-maintain template with very simple functionality.
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<p><blockquote><i>
<tmpl_var pull_quote>
</i></blockquote></p>
</tmpl_if>
</tmpl_loop>
Story.tmpl
By abstracting out Page's element_loop, Story.tmpl can now also be simplified a bit:
<tmpl_loop page_loop>
<html>
<head>
<title><tmpl_var title></title>
</head>
<body>
<tmpl_if __first__>
<h1><tmpl_var title></h1>
<b><tmpl_var deck></b>
</tmpl_if>
<tmpl_var page>
Page Number: <tmpl_var page_count>
</body> </html>
<tmpl_unless __last__>
<tmpl_var page_break>
</tmpl_unless>
</tmpl_loop>
Questions:
Go back and re-read How Krang Builds Template Data. Each element within <tmpl_loop page_loop> will populate <tmpl_var page> (containing the output from the page element), and <tmpl_var page_count> (which tells you what the number of the element is in the loop).
They are called page elements. Each page should be considered separate, the page_break tag will tell Krang to create a new page for the next page element's content.
What's the deal with <tmpl_if __first__> and <tmpl_unless __last__> ?__first__ and __last__ are loop variables that are always made available within the context of a <tmpl_loop>.
__first__ returns true if you are on the first iteration through a loop, false otherwise.
__last__ returns true if you are on the last iteration through a loop, false otherwise.
In this case, we're putting the story title and deck at the head of the first page only, and putting in a page break on every page except the last one. Since we only have one page to this story, there's only one page break.
Results:
You can create templates for any element you choose. For example, the Pull Quote element. If you wanted all Pull Quotes to be identical in appearance, you could create a template specific to the Pull Quote.
Pull_Quote.tmpl
<p><blockquote><i> <tmpl_var pull_quote> </i></blockquote></p>
Now whenever Krang attempts to publish a Pull Quote element, rather than simply return the data stored within the Pull Quote element, it will return the formatted output.
The Page template can now stop formatting Pull Quotes:
Page.tmpl
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<tmpl_var pull_quote>
</tmpl_if>
</tmpl_loop>
Every template created in Krang is associated with a category for a very specific reason: When publishing a story, Krang uses the category under which that story is being published to determine where to look for element templates.
The process by which an element's template is chosen works as follows:
www.foo.com/bar/baz/.
For the element Page, the Krang publish system will start looking in the template repository for page.tmpl in www.foo.com/bar/baz/page.tmpl. As soon as it finds page.tmpl, it will stop looking and continue the publish process using the newly-found template.
If page.tmpl is not found there, it will move up directories, continuing to look for page.tmpl. www.foo.com/bar/page.tmpl, followed by www.foo.com/page.tmpl.
The last place it will check is the root category (underneath the site www.foo.com). If it cannot find the required template at this point, an error message will be generated.
As you build your site, you can now customize the appearance of your site on a site-by-site and category-by-category basis.
A further example - you have a story publishing under two different categories: www.foo.com/foo and www.foo.com/foo/bar/baz/. The basic pull_quote.tmpl template doesn't do anything special, it simply returns the Pull Quote. But for the purposes of better design, the second category requires Pull Quotes in an entirely different format. But at the same time, you don't want to affect the look of the story as it's published in the first category.
By creating a new pull_quote.tmpl for the category www.foo.com/foo/bar/baz/ with the necessary formatting, your job is done. Only stories published under www.foo.com/foo/bar/baz/ (or the categories beneath it) will use that Pull Quote template, everything else will continue on unaffected.
Contributors in Krang are attached directly to stories. They are made available to all elements as a list (tmpl_loop) of contributors, under the loop contrib_loop.
Variables
The variables that make up the contributors are as follows:
Within each element, the following variables are available (corresponding to the parameters available within the Contributers interface):
Within this loop, there are two fields:
Usage
For the purposes of this example, we have three contributors - two writers, Felix D. Cat and John Johnz, and a photographer, V. Molleux. The template stub that we would use might look something like this:
<tmpl_if contrib_loop>
By:
<tmpl_loop contrib_loop>
<!-- Determine whether we need a comma or an "and" to separate -->
<tmpl_unless __first__>
<tmpl_if __last__>
and
<tmpl_else>
,
</tmpl_if>
</tmpl_unless>
<!-- First Middle Last (Job) -->
<tmpl_var first> <tmpl_var middle> <tmpl_var last>
<tmpl_if contrib_type_loop>
(
<tmpl_loop contrib_type_loop>
<tmpl_var contrib_type_name>
<tmpl_if __last__>)</tmpl_if>
</tmpl_loop>
</tmpl_if>
<!-- Image -->
<tmpl_if image_url>
<img src=<tmpl_var image_url>>
</tmpl_if>
</tmpl_loop>
</tmpl_if>
The end result would be a line that read like this:
By: Felix D. Cat (Writer), John Johnz (Writer) and V. Molleux (Photographer)
Templates
In the context of a larger template, like Page.tmpl, it might look as follows:
Page.tmpl
<h1><tmpl_var title></h1> <b><tmpl_var deck></b>
<!-- Contributors -->
<tmpl_if contrib_loop>
By:
<tmpl_loop contrib_loop>
<!-- Determine whether we need a comma or an "and" to separate -->
<tmpl_unless __first__>
<tmpl_if __last__>
and
<tmpl_else>
,
</tmpl_if>
</tmpl_unless>
<!-- First Middle Last (Job) -->
<tmpl_var first> <tmpl_var middle> <tmpl_var last>
<tmpl_if contrib_type_loop>
(
<tmpl_loop contrib_type_loop>
<tmpl_var contrib_type_name>
<tmpl_if __last__>)</tmpl_if>
</tmpl_loop>
</tmpl_if>
<!-- Image -->
<tmpl_if image_url>
<img src=<tmpl_var image_url>>
</tmpl_if>
</tmpl_loop>
</tmpl_if>
<!-- /Contributors -->
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<p><blockquote><i>
<tmpl_var pull_quote>
</i></blockquote></p>
</tmpl_if>
</tmpl_loop>
Links to other stories are provided by whatever element subclasses Krang::ElementClass::StoryLink. Something to remember here is that Krang does not put forth any rules as to the naming of elements in an element library - so an element that subclasses to Krang::ElementClass::StoryLink could be called anything.
NOTE: While the lack of explicit element naming in Krang may sound odd, consider the possibilities - you can create multiple elements, all which provide links to other stories in Krang (e.g. they all subclass Krang::ElementClass::StoryLink), but each element can now have its own template, outputting links in its own way.
For the purposes of this example, the element will be called 'leadin'.
Story Tree
Taking the story tree from Example 2, we'll add a leadin element to the page:
Story
- Deck (subclass of Krang::ElementClass::Text)
+ Page (subclass of Krang::ElementClass)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Pull Quote (subclass of Krang::ElementClass::Text)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Leadin (subclass of Krang::ElementClass::StoryLink)
Templates
StoryLink elements can function with or without their own templates.
Leadin.tmpl
<a href="<tmpl_var escape=html url>"><tmpl_var title></a>
Page.tmpl
<h1><tmpl_var title></h1> <b><tmpl_var deck></b>
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<tmpl_var pull_quote>
</tmpl_if>
<tmpl_if is_leadin>
<p><tmpl_var leadin></p>
</tmpl_if>
</tmpl_loop>
Page.tmpl - alternate
<h1><tmpl_var title></h1> <b><tmpl_var deck></b>
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<tmpl_var pull_quote>
</tmpl_if>
<tmpl_if is_leadin>
<p><a href="<tmpl_var escape=html url>"><tmpl_var title></a></p>
</tmpl_if>
</tmpl_loop>
Links to media objects are provided by whatever element (or elements) subclasses Krang::ElementClass::MediaLink. In short, these elements will behave in the same way as elements that subclass Krang::ElementClass::StoryLink (see Links to Other Stories in Krang in the section above).
NOTE: Expect to use more than one element in your handling of media objects in Krang, simply because different media objects require different HTML. Consider this - do you really want the URL to your Flash or PDF file embedded in an <img src=> tag?
For the purposes of this example, the element that handles links to Media Objects will be called 'image'.
Story Tree
Taking the story tree from Example 2, we'll add a leadin element to the page:
Story
- Deck (subclass of Krang::ElementClass::Text)
+ Page (subclass of Krang::ElementClass)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Pull Quote (subclass of Krang::ElementClass::Text)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Image (subclass of Krang::ElementClass::MediaLink)
Templates
StoryLink elements can function with or without their own templates.
Image.tmpl
<img src="<tmpl_var escape=html url>">> <br>
<tmpl_if title> <tmpl_var title><br> </tmpl_if>
<tmpl_if caption> <tmpl_var caption><br> </tmpl_if>
Page.tmpl
<h1><tmpl_var title></h1> <b><tmpl_var deck></b>
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<tmpl_var pull_quote>
</tmpl_if>
<tmpl_if is_image>
<p><tmpl_var image></p>
</tmpl_if>
</tmpl_loop>
Page.tmpl - alternate
<h1><tmpl_var title></h1> <b><tmpl_var deck></b>
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<tmpl_var pull_quote>
</tmpl_if>
<tmpl_if is_image>
<img src="<tmpl_var escape=html url>">>
<br>
<tmpl_if title>
<tmpl_var title><br>
</tmpl_if>
<tmpl_if caption>
<tmpl_var caption><br>
</tmpl_if>
</tmpl_if>
</tmpl_loop>
A whole set of template variables relating to pagination are created by Krang for specific elements. These variables are built whenever Krang encounters an element with the attribute pageable set to 1 (Generally the page element -- See Creating Element Libraries in Krang for more information).
Variables
For pageable elements, the following variables are made available:
The variables in each loop element are as follows:
page_number = current_page_number.
page_number = current_page_number.
Updated Story Tree
Since pagination isn't very interesting with only one page, let's add a second page to the previous Story Tree. Furthermore, since the two pages are entirely separate constructs, there's no reason why the second page has to be identical to the first in structure.
Story
- Deck (subclass of Krang::ElementClass::Text)
+ Page (subclass of Krang::ElementClass)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Pull Quote (subclass of Krang::ElementClass::Text)
- Paragraph (subclass of Krang::ElementClass::TextArea)
+ Page (subclass of Krang::ElementClass)
- Pull Quote (subclass of Krang::ElementClass::Text)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Paragraph (subclass of Krang::ElementClass::TextArea)
- Paragraph (subclass of Krang::ElementClass::TextArea)
Templates
Continuing with the templates from the previous example, all the pagination logic will be found in Page.tmpl. As a result, Story.tmpl becomes further simplified, as you will see.
Page.tmpl
<!-- Test for first page here -->
<tmpl_if is_first_page>
<h1><tmpl_var title></h1>
<b><tmpl_var deck></b>
</tmpl_if>
<tmpl_loop element_loop>
<tmpl_if is_paragraph>
<p><tmpl_var paragraph></p>
</tmpl_if>
<tmpl_if is_pull_quote>
<tmpl_var pull_quote>
</tmpl_if>
</tmpl_loop>
<!-- Pagination at the bottom of the page -->
<P>Currently reading page <tmpl_var current_page_number> of <tmpl_var total_pages>.<BR>
<tmpl_unless is_first_page> <a href="<tmpl_var previous_page_url>">Previous Page</a> </tmpl_unless>
<tmpl_loop pagination_loop>
<tmpl_if is_current_page>
<tmpl_var page_number>
<tmpl_else>
<a href="<tmpl_var page_url>"><tmpl_var page_number></a>
</tmpl_if>
</tmpl_loop>
<tmpl_unless is_last_page> <a href="<tmpl_var next_page_url>">Next Page</a> </tmpl_unless>
<!-- /Pagination -->
Notice a few things that used to exist in Story.tmpl? The <tmpl_var title> and <tmpl_var deck> that should only appear on the first page can now be placed here. And the <tmpl_var page_count> that used to provide an element (page) count is now pretty redundant as well.
The new Story.tmpl looks a lot smaller now:
Story.tmpl
<tmpl_loop page_loop>
<html>
<head>
<title><tmpl_var title></title>
</head>
<body>
<tmpl_var page>
</body> </html>
<tmpl_unless __last__>
<tmpl_var page_break>
</tmpl_unless>
</tmpl_loop>
Note - it is possible to move <tmpl_var page_break> into Page.tmpl as well, but would require moving around a lot of HTML and other page logic, in this case it's simply easier to leave it in place.
Just as elements can have templates, category elements may have a template associated with it called category.tmpl.
For an example, let's imagine that we want to put a blue box around every page in our output. Instead of putting this HTML into our templates we'll do it in a category template.
The way this all works is by using the tag <tmpl_var content>. This tag is handled internally by Krang, where it will replace the tag with the output of the Story element (Story.tmpl).
Templates
Category.tmpl
<html> <head><tmpl_var title></head> <body>
<table bgcolor=blue cellspacing=5 border=0><tr><td>
<tmpl_var content>
</td></tr></table>
</body> </html>
Note that since the Category.tmpl template now makes up the header and footer of the document, Story.tmpl becomes greatly simplified:
Story.tmpl
<tmpl_loop page_loop>
<tmpl_var page>
<tmpl_unless __last__>
<tmpl_var page_break>
</tmpl_unless>
</tmpl_loop>
NOTE: Yes, the page-break logic is still correct in Story.tmpl. Krang will take each page of output generated by Story.tmpl, and merge that page with Category.tmpl.
The above example shows how to integrate category templates with your story templates, but isn't the end of things. Like stories, categories can (and usually do) have child elements as well. How do they work? In exactly the same fashion as child elements in the Story tree.
Sample Story Tree
Here we have a category which displays its own name, two links for the left-side navigation, and one link for the foother.
- category (subclasses Krang::ElementClass::TopLevel)
- display_name (subclasses Krang::ElementClass::Text)
- left_nav_link (subclasses Krang::ElementClass::CategoryLink)
- left_nav_link (subclasses Krang::ElementClass::CategoryLink)
- footer_link (subclasses Krang::ElementClass::CategoryLink)
The template using this element tree might look as follows:
Category.tmpl
<html> <head><tmpl_var title></head> <body>
<table border=0> <tr>
<!-- Left Nav -->
<td>
<tmpl_loop element_loop>
<tmpl_if is_display_name>
<tmpl_var display_name><BR>
</tmpl_if>
<tmpl_if is_left_nav_link>
<tmpl_var is_left_nav_link><BR>
</tmpl_if>
</tmpl_loop>
</td>
<!-- /Left Nav -->
<!-- Story --> <td> <tmpl_var content> </td> <!-- /Story -->
</tr> </table>
<!-- Footer -->
<table border=0>
<tr>
<tmpl_loop element_loop>
<tmpl_if footer_link>
<td><tmpl_var footer_link></td>
</tmpl_if>
</tmpl_loop>
</tr>
</table>
<!-- /Footer -->
</body> </html>
This document only begins to cover what you can do in Krang. If you are comfortable with programming in Perl, consider learning more about the construction and customization of elements and element libraries by reading Customizing the Publish Process in Krang.