Writing HTML::Template Templates in Krang


Name

Writing HTML::Templates in Krang - A guide to using the Krang templating system.


Introduction

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.


Templates in Krang

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.


Publish Methods and Templates

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.


Choosing a Strategy

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.


Publishing With Stock Krang Publish Methods

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.

An Example Story Type

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.

How Krang Builds Template Data

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:

Example 1: Using One Large Template (For Each Story Type)

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:

Example 2: Using Multiple Templates (For Each Story Type)

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:

Results:

The Next Step

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>


Varying Templates and Output by Category -- Overriding Templates

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:

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.


Additional Publishing Components

Contributors

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:

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 in Krang

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 in Krang

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.

Pagination

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:

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.

Category Templates

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.

Category Child Elements

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>


Conclusion

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.