Looping

Looping is basically about repeating a template block and filling each repeated item differently. There are two ways of looping: → node.iterate() and → node.repeat(). Looping with → node.iterate() looks like this:

from tdi import html
template = html.from_string("""
<html>
<body>
    <ul>
        <li tdi="menu"><a tdi="link">some menu item</a></li>
    </ul>
</body>
</html>
""")

class Model(object):
    def __init__(self, menu, page):
        self._menu = menu
        self._page = page

    def render_menu(self, node):
        items = enumerate(node.iterate(self._menu))
        for idx, (subnode, (href, menuitem)) in items:
            subnode.link.content = menuitem
            if (idx + 1 == self._page):
                subnode.link.hiddenelement = True
            else:
                subnode.link['href'] = href

menu = [
    (u'/some/', u'Some Menu Item'),
    (u'/other/', u'Editing Content & Attributes'),
    (u'/third.html', u'Another Menu Item'),
]
model = Model(menu=menu, page=2)
template.render(model)

The → repeat() method uses a callback function instead of the loop body for the logic:

    def render_menu(self, node):
        node.repeat(self.repeat_menu, self._menu)

    def repeat_menu(self, node, (href, menuitem)):
        node.link.content = menuitem
        if (node.ctx[0] + 1 == self._page):
            node.link.hiddenelement = True
        else:
            node.link['href'] = href

The output is the same:

<html>
<body>
    <ul>
        <li><a href="/some/">Some Menu Item</a></li><li>Editing Content &amp; Attributes</li><li><a href="/third.html">Another Menu Item</a></li>
    </ul>
</body>
</html>

The main differences between those methods are:

  • The result of iterate() needs to be buffered until the render_name method is done. repeat() is not buffered. Thus for huge loops repeat() is the preferred method.
  • If you’re using iterate(), you should handle all child nodes within the loop, as they are marked as “done”, i.e. no render methods are called for them. repeat() has no such effect.

repeat() also fills the ctx attribute of the node (and all of its child nodes), which is a tuple containing:

  1. The number of the repetition (starting with zero)
  2. The current item
  3. The custom parameter tuple (the “fixed” argument in the → repeat() function signature.)

Looping With Separators

It’s often needed to also put blocks between repeated items. TDI calls these blocks separators. A common case is to just put a static piece of template code between, without doing anything special with the separator block itself.

<html>
<body>
    <ul>
        <li tdi="menu"><a tdi="link">some menu item</a></li><li tdi=":-menu">
        </li>
    </ul>
</body>
</html>

The separator node has to be defined on the same level as the (potential) list item node. It has to get the same name, but is decorated with the : flag. Separator nodes are special, they are attached to the accompanying “real” node and are used only if this “real” node is repeated (with more than one resulting item). Otherwise the separators just vanish from the output.

In the case above the separator simply adds some space characters, since the element is hidden (because of the - flag). The rest of the example (the python code) is stolen from the looping example above. The output is, of course, different:

<html>
<body>
    <ul>
        <li><a href="/some/">Some Menu Item</a></li>
        <li>Editing Content &amp; Attributes</li>
        <li><a href="/third.html">Another Menu Item</a></li>
    </ul>
</body>
</html>

If you want to modify separator nodes you can:

  • define a separate_name method, which will be called automatically if a separator is needed.
  • Pass a callback function to the → iterate() or → repeat() methods.

Inside a separator function you can access the context information (node.ctx) as well. In this case the current item is actually a tuple containing both the last and the next iterated item.

Here’s an example modifying the last separator:

from tdi import html
template = html.from_string("""
<html>
<body>
    <p>My fruit salad contains <tdi tdi="-fruit">Apples</tdi><tdi
    tdi=":-fruit">, </tdi>.</p>
</body>
</html>
""".lstrip())

class Model(object):
    def __init__(self, fruits):
        self._fruits = fruits

    def render_fruit(self, node):
        node.repeat(self.repeat_fruit, self._fruits, len(fruits) - 2)

    def repeat_fruit(self, node, fruit, last_sep_idx):
        node.content = fruit

    def separate_fruit(self, node, last_sep_idx):
        if node.ctx[0] == last_sep_idx:
            node.content = u' and '


fruits = [
    u'apples', u'pears', u'bananas', u'pineapples',
]
model = Model(fruits)
template.render(model)
<html>
<body>
    <p>My fruit salad contains apples, pears, bananas and pineapples.</p>
</body>
</html>