TDI allows you to bind nodes or groups of nodes to scopes. This is the counterpart to overlays on the code side. Scopes add the capability to edit the template with different model instances.
It looks like this:
<html>
<body>
<h1 tdi="doctitle">doc title goes here</h1>
<ul tdi:scope="menu">
<li tdi="item"><a tdi="link">some menu item</a></li><li tdi=":-item">
</li>
</ul>
<p tdi="intro" class="edit-intro">Intro goes here.</p>
<div class="list" tdi="list">
...
</div>
</body>
</html>
Everything within the node marked with tdi:scope (including the node itself) belongs to the “menu” scope. Each scope eventually refers to its own model object, which is looked up as scope_name within the current model. Here’s the code for our menu example:
class Menu(object):
def __init__(self, menu, page):
self._menu = menu
self._page = page
def render_item(self, node):
node.repeat(self.repeat_item, self._menu)
def repeat_item(self, node, (href, menuitem)):
node.link.content = menuitem
if (node.ctx[0] + 1 == self._page):
node.link.hiddenelement = True
else:
node.link['href'] = href
class Model(object):
def __init__(self, menu, page):
self.scope_menu = Menu(menu, page)
The essence is, that scopes allow you to split your model code into logical pieces. Again, which piece of code belongs to which scope is part of the logic, not part of the template.
More notes on scopes:
The output of the example:
<html>
<body>
<h1>doc title goes here</h1>
<ul>
<li><a href="/some/">Some Menu Item</a></li>
<li>Editing Content & Attributes</li>
<li><a href="/third.html">Another Menu Item</a></li>
</ul>
<p class="edit-intro">Intro goes here.</p>
<div class="list">
...
</div>
</body>
</html>
Scopes do not need to be contained by one block. You can easily scatter them over the template. If they occur within the same parent scope, the same scope model instance will be asked for rendering. That’s very handy for distributed but logically associated content, for example:
from tdi import html
template = html.from_string("""
<html>
<body>
<ul tdi:scope="navigation">
<li tdi="breadcrumb"><a tdi="*link"></a></li><li tdi=":-breadcrumb">
</li>
</ul>
...
<ul tdi:scope="navigation">
<li tdi="menu"><a tdi="*link">some menu item</a></li><li tdi=":-menu">
</li>
</ul>
</body>
</html>
""".lstrip())
class Navigation(object):
def __init__(self, breadcrumb, menu, page):
self._breadcrumb = breadcrumb
self._menu = menu
self._page = page
def render_breadcrumb(self, node):
node.repeat(self.repeat_nav, self._breadcrumb,
len(self._breadcrumb) - 1
)
def render_menu(self, node):
node.repeat(self.repeat_nav, self._menu,
self._page - 1
)
def repeat_nav(self, node, (href, menuitem), active):
node.link.content = menuitem
if (node.ctx[0] == active):
node.link.hiddenelement = True
else:
node.link['href'] = href
class Model(object):
def __init__(self, breadcrumb, menu, page):
self.scope_navigation = Navigation(breadcrumb, menu, page)
menu = [
(u'/some/', u'Some Menu Item'),
(u'/other/', u'Editing Content & Attributes'),
(u'/third.html', u'Another Menu Item'),
]
breadcrumb = [
(u'/', u'Hompage'),
(u'/other/', u'Editing Content & Attributes'),
]
model = Model(breadcrumb=breadcrumb, menu=menu, page=2)
template.render(model)
Also note, how the repeater code is reused.
<html>
<body>
<ul>
<li><a href="/">Hompage</a></li>
<li>Editing Content & Attributes</li>
</ul>
...
<ul>
<li><a href="/some/">Some Menu Item</a></li>
<li>Editing Content & Attributes</li>
<li><a href="/third.html">Another Menu Item</a></li>
</ul>
</body>
</html>
There’s another, more tricky use case. You can extend or parametrize an existing scope from within the template. That’s useful when you have overlays, which provide standard constructions. Examples are layouted boxes, flash embedding code and so on. The idea is to place a parameter block near the overlay target (usually before it, so it can trigger code, before the overlay is actually rendered). The parameter block gets the same scope as the overlay and voilà – the scope model will see it. The following code outlines the priniciple:
from tdi import html
from tdi.tools.html import decode
# This is some typcial flash embedding code.
# It serves as a "layout widget", placed via overlay
flash = html.from_string("""
<!-- <tdi> is used as neutral dummy element (removed due to the - flag) -->
<tdi tdi:scope="-flash" tdi:overlay="<-flash">
<object tdi="object_ie"
classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width=""
height="">
<param tdi="url" name="movie" value="" />
<param tdi="param" />
<![if !IE]><object tdi="object_other"
type="application/x-shockwave-flash" data="" width="" height="">
<param tdi="param" />
<![endif]>
<p tdi="alternative">You need to enable <a
href="http://www.adobe.com/go/getflashplayer">Flash</a> to view this
content.</p>
<![if !IE]></object><![endif]></object>
</tdi>
""")
# page template, using the flash layout widget, passing parameters
template = html.from_string("""
<html>
<body>
<h1>some flash</h1>
<tdi tdi:scope="-flash.param" tdi="-init"
file="flashfile.swf" width="400" height="300">
<tdi tdi="param" name="bgcolor" value="#ffffff" />
<tdi tdi="param" name="wmode" value="transparent" />
<tdi tdi="param" name="quality" value="high" />
<img tdi="alternative" src="replacement-image.png" alt="some cool text" />
</tdi>
<tdi tdi:overlay="->flash" />
</body>
</html>
""".lstrip()).overlay(flash)
class FlashParam(dict):
def render_init(self, node):
self.clear()
encoding = node.raw.encoder.encoding
self['file'] = decode(node[u'file'], encoding)
self['width'] = decode(node[u'width'], encoding)
self['height'] = decode(node[u'height'], encoding)
self['param'] = []
node.render()
node.remove()
def render_param(self, node):
encoding = node.raw.encoder.encoding
self['param'].append((
decode(node[u'name'], encoding),
decode(node[u'value'], encoding),
))
def render_alternative(self, node):
self['alt'] = node.copy()
class Flash(object):
def __init__(self):
self.scope_param = FlashParam()
def render_object_ie(self, node):
p = self.scope_param
node[u'width'] = p['width']
node[u'height'] = p['height']
def render_url(self, node):
p = self.scope_param
node[u'value'] = p['file']
def render_param(self, node):
p = self.scope_param
for pnode, (name, value) in node.iterate(p['param']):
pnode[u'name'] = name
pnode[u'value'] = value
def render_object_other(self, node):
p = self.scope_param
node[u'width'] = p['width']
node[u'height'] = p['height']
node[u'data'] = p['file']
def render_alternative(self, node):
p = self.scope_param
if 'alt' in p:
node.replace(None, p['alt'])
class Model(object):
def __init__(self):
self.scope_flash = Flash()
template.render(Model())
<html>
<body>
<h1>some flash</h1>
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="400" height="300">
<param name="movie" value="flashfile.swf" />
<param name="bgcolor" value="#ffffff" /><param name="wmode" value="transparent" /><param name="quality" value="high" />
<![if !IE]><object type="application/x-shockwave-flash" data="flashfile.swf" width="400" height="300">
<param name="bgcolor" value="#ffffff" /><param name="wmode" value="transparent" /><param name="quality" value="high" />
<![endif]>
<img src="replacement-image.png" alt="some cool text" />
<![if !IE]></object><![endif]></object>
</body>
</html>
This code probably needs some explanations:
Now, while it looks like a lot of effort to build such a code – remember you’re going to create that once and use often. It will save time and nerves directly afterwards.
You can declare absolute scopes by flagging them with =. This means, they break out of the current scope nesting. You should avoid using this feature regularily. It is, however, useful for: