Code Partitioning With Scopes¶
TDI allows you to bind nodes or groups of nodes to scopes. This is the counterpart to overlays on the python 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:
- They can be nested and work cumulative. A scope
menu
inside a scopeleft_bar
is looked up asmodel.scope_left_bar.scope_menu
. - dotted scope names are allowed (for example
header.menu
), which means that two scopes (firstheader
and thenmenu
inside theheader
model) are looked up (model.scope_header.scope_menu
) - scopes can be combined with overlays, of course (you can specify both
tdi:scope
andtdi:overlay
within the same node)
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>
Distributed Scopes¶
Simple Scope Scattering¶
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>
Parametrized Overlays¶
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:
- it uses a
<tdi>
element as “neutral” container for some stuff. This is often useful if you need such a semantic-less element. It also fits everywhere (which a<span>
cannot contain everything and a<div>
cannot be contained by everything). - it uses directly a subscope for the parameters. That’s not exactly
neccessary, but it’s useful sometimes – in this case, because it
separates the parameters and the (flash-)container rendering on the
code side. Note how the
FlashParam
scope model inherits fromdict
. FlashParam.render_init
uses a subtle trick to render its children and the remove itself: subrendering. This may seem like a cheap trick, but it’s not. The generated result string (which is thrown away) is very small and it keeps the code very clear.- However, we just invented our own parameter language here. That’s not a good thing per se. Keep these things small and restrict them to the absolutely neccessary cases. They are the source of confusion and error.
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.
Absolute Scopes¶
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:
- generated templates, in order to ensure a certain structure (it’s used by the prerendering-mechanism, for example)
- truly absolute scopes, which are always located at the root model (like statistics snippets, parametrized layout widgets, or, say, the main navigation)