Tutorial 2: Repeating groups of nodes

About this tutorial

When designing an HTML template, you'll sometimes need to insert additional HTML elements (typically div or span) to allow you to define template nodes in the proper locations. For example, given the data:

sections = [('title 1', 'description 1'), 
            ('title 2', 'description 2'), 
            ('title 3', 'description 3')]

to generate a page like the following, you need to repeat the h2 and p elements for each tuple in the list:

<h2>title 1</h2>
<p>description 1</p>

<h2>title 2</h2>
<p>description 2</p>

<h2>title 3</h2>
<p>description 3</p>

A common mistake for beginners is to write:

<h2 node="rep:title">section title</h2>
<p node="rep:desc">section description</p>

but this template generates the following output, which is not what you want:

<h2>title 1</h2>
<h2>title 2</h2>
<h2>title 3</h2>
<p>description 1</p>
<p>description 2</p>
<p>description 3</p>

The solution is to group the h2 and p elements within a single Repeater node and repeat that.

1. Group the elements

Wrap the related h2 and p elements in a generic div element:

<div>
    <h2>section title</h2>
    <p>section description</p>
</div>

2. Add a Repeater directive

Mark the div element as a Repeater (rep) node named section and the h2 and p elements as Container (con) nodes:

<div node="rep:section">
    <h2 node="con:title">section title</h2>
    <p node="con:desc">section description</p>
</div>

Now wrap this HTML as a Python string and compile it into a Template object.

3. Implement the controller callback functions

Define a callback function named render_section that controls how the section information is inserted into the rep:section node's con:title and con:desc sub-nodes. This function should take a copy of the Repeater node as its first parameter and a (title,description) tuple as its second:

def render_section(node, section):
    node.title.text, node.desc.text = section

Define a render_template function that takes a copy of the Template object as its first parameter and a list of (title,description) tuples as its second. It should then call the rep:section node's repeat method, passing it the render_section function and the list of section information tuples:

def render_template(node, sections):
    node.section.repeat(render_section, sections)

Now add the code to call the Template object's render method, passing the render_template function and section data list as arguments, and print the result.

4. Render the template

Here is the completed tutorial script:

from htmltemplate import Template

template  = Template("""
<div node="rep:section">
    <h2 node="con:title">section title</h2>
    <p node="con:desc">section description</p>
</div>
""")

def render_section(node, section):
    node.title.text, node.desc.text = section

def render_template(node, sections):
    node.section.repeat(render_section, sections)


sections = [('title 1', 'description 1'), 
            ('title 2', 'description 2'), 
            ('title 3', 'description 3')]
print(template.render(render_template, sections))

When rendered, this template will generate the following HTML:

<div>
    <h2>title 1</h2>
    <p>description 1</p>
</div>
<div>
    <h2>title 2</h2>
    <p>description 2</p>
</div>
<div>
    <h2>title 3</h2>
    <p>description 3</p>
<div>

5. Tidy the output

If the <div> and </div> tags serve no purpose in the rendered page, you can omit them by prefixing the rep:section directive with an 'omit tags' modifier (-):

<div node="-rep:section">

Here's how the generated HTML now looks:

    <h2>title 1</h2>
    <p>description 1</p>


    <h2>title 2</h2>
    <p>description 2</p>


    <h2>title 3</h2>
    <p>description 3</p>