Understanding the template object model

About the TOM

htmltemplate's template object model ('TOM') is similar in concept to the Document Object Model found in web browsers, in that both parse an HTML document into a hierarchical tree structure which can then be manipulated via a programmatic API. However, while the DOM implements a large, complex API allowing general manipulation of every element in the document, the TOM provides a very small, simple, callback-based API for performing templating-specific operations on selected elements only.

A template object model is constructed from three classes: Template, Container and Repeater. The Template object is the template object model's root node, representing the complete HTML document. Each Container and Repeater node describes an element within the template HTML whose content and/or attributes can be dynamically manipulated by Python code.

Each Container node has a one-to-one relationship with its parent node, i.e. it will appear only once in a rendered document. Each Repeater node has a one-to-many relationship with its parent, i.e. it will appear zero or more times in the output. The Template node can contain any number of Container and/or Repeater sub-nodes, each of which may contain its own sub-nodes, and so on.

The Template node

As well as representing the template object model's root, the Template class is also responsible for parsing an HTML template whenever its __init__ method is called. For example, Tutorial 1 used the following code to create a template object model from an HTML template string:

template = Template("""
<html>
    <head>
        <title node="con:title">TITLE</title>
    </head>
    <body>
        <ul>
            <li node="rep:item">
                <a href="#" node="con:link">LINK</a>
            </li>
        </ul>
    </body>
</html>
""")

Each time the template parser encounters a special 'node' attribute containing a con or rep directive, it creates the corresponding Container or Repeater object at the appropriate point in the template object model.

Once the template HTML is successfully parsed, the resulting Template object can be used to generate any number of HTML documents. The simplest way to generate an HTML document is by calling Template.render(), passing a callback function as its first argument, followed by any data the function might need such as the values to be inserted:

title = "Site Map"
links = [('index.html', 'Home'), ('products/index.html', 'Products'), ('about.html', 'About')]

template.render(render_template, title, links)

This 'controller' function should contain the code responsible for manipulating the Template node's immediate sub-nodes – inserting content, setting tag attributes, deleting unwanted nodes, and so on. For example, the following render_template function sets the content of the Container sub-node named title and tells the Repeater sub-node named item to render copies of itself using the given list:

def render_template(node, pagetitle, linkinfos):
    node.title.text = pagetitle
    node.item.repeat(render_item, linkinfos)

Before calling the controller function, the render method first creates a full copy of the original template object model, and it is this copy which is passed to the function to manipulate. Since the original Template object is not modified, it can be reused as many times you like without the need to re-parse it each time.

Once the controller function has finished its work, the render method converts the modified object model back to HTML and returns the result.

(Note that while this all-in-one approach is the easiest way to generate documents, htmltemplate also allows you to perform each step separately should you need to customize the copy, manipulate, and/or render operations. See the 'Fine-tuning the rendering process' section in the Notes chapter for details.)

Container nodes

A Container node represents a modifiable HTML element that will normally appear once in the finished page, e.g.:

<title node="con:title">TITLE</title>

The HTML element may be empty (e.g. <br/>) in which case it has no content and only its tag attributes are modifiable, or non-empty (e.g. <p>...</p>) in which case it may contain either modifiable content (plain text/markup) or other Container and/or Repeater nodes.

Container nodes can be manipulated in various ways:

See the Class definitions chapter for a full list of available properties and methods.

Repeater nodes

A Repeater node is similar to a Container node, except that it can appear any number of times in the finished page:

<li node="rep:item">...</li>

Repeater objects support all of the properties and methods provided by Container nodes, plus two additional methods, repeat and add. Unlike a Container node, which appears by default in the rendered output, a Repeater node will not appear at all unless its add or repeat method is called.

The add method works a lot like the main render method in that its first argument is a 'controller' callback function that is responsible for manipulating a copy of the original node: inserting content, modifying tag attributes, manipulating its sub-nodes, and so on. Any values to be used by the callback function can be passed as additional arguments. Unlike render, however, the add method does not return the rendered HTML but instead stores it within the original Repeater node. Each time add is called, another copy of the Repeater node is copied, manipulated, rendered and stored. For example, Tutorial 1's main render_template controller function could be implemented like so:

def render_template(node, pagetitle, linkinfos):
    node.title.text = pagetitle
    for linkinfo in linkinfos:
        node.item.add(render_item, linkinfo)

While the add method provides flexibility, the repeat method supports a more streamlined approach in the most common use case: iterating over a single list. As with add, the repeat method's first argument is a controller function. This should be followed by a second argument, which is the list to iterate over. Any subsequent arguments will be forwarded to the callback function as before. Thus the above render_template function can be more concisely written as:

def render_template(node, pagetitle, linkinfos):
    node.title.text = pagetitle
    node.item.repeat(render_item, linkinfos)

The repeat method will iterate over the given list, cloning the original Repeater node and passing the copy to the controller function to manipulate, once for each item in the list. If a particular iteration is not needed, the controller function can call the cloned node's omit method before it returns. The parent controller function can also call the original Repeater node's omit method to omit all iterations. The following example demonstrates how to do both:

<ul>
    <li node="rep:item">ITEM</li>
</ul>

def render_item(node, value):
    if value:
        node.text = value
    else:
        node.omit() # omit just this instance of the 'item' node

def render_template(node, values, isvisible):
    if isvisible:
        node.item.repeat(render_item, values)
    else:
        node.item.omit() # omit all instances of the 'item' node

Notes

Like the Template class, Container and Repeater classes also support a render method that renders either a copy or the original depending on whether or not a controller function is passed as an argument. While not often needed, this can be useful if you need to render just that portion of the template.

Nodes support a number of introspection-related features: