render_pattern: Repeat patterns easily in Nevow templates
After render_fragment, dialtone mentioned render_pattern, that would get one or many patterns from the page and put them in the current tag. Well, that's easy to write:
def render_pattern(self, name):
"""
Find and render a pattern.
Example:
<span nevow:pattern="foo">
I'm very repetititive.
</span>
<ul>
<li nevow:render="pattern foo">
this text will get removed when rendering
</li>
<li nevow:render="pattern foo"/>
</ul>
"""
def f(ctx, data):
doc = self.docFactory.load(ctx)
patterns = inevow.IQ(doc).allPatterns(name)
return ctx.tag.clear()[patterns]
return f
Updated to adapt doc to inevow.IQ before calling allPatterns.
render_fragment: Reusable fragment embedding in Nevow templates
This Nevow renderer came up on #twisted.web. Thanks to rwall and dialtone for input.
def render_fragment(self, name):
"""
Find and render a fragment, with optional docFactory.
Find a fragment factory from self via attributes named
fragment_* and replace content of current tag with said
fragment.
If pattern docFactory is found under this tag, pass it as
docFactory to the fragment factory.
Example:
class MyFrag(rend.Fragment):
...
class MyPage(rend.Page):
fragment_foo = MyFrag
...
and give MyPage a template with
<!-- no docFactory -->
<div nevow:render="fragment foo">
this text will get removed when rendering
</div>
<!-- with docFactory -->
<div nevow:render="fragment foo">
this text will get removed when rendering
<span nevow:pattern="docFactory">
but this whole tag will be passed as docFactory to MyFrag.
</span>
</div>
"""
def f(ctx, data):
callable = getattr(self, 'fragment_%s' % name, None)
if callable is None:
callable = lambda ctx, *args: ctx.tag[
"The fragment named '%s' was not found in %r." % (name, self)]
kwargs = {}
try:
docFactory = ctx.tag.onePattern('docFactory')
except stan.NodeNotFound:
pass
else:
kwargs['docFactory'] = loaders.stan(docFactory)
return ctx.tag.clear()[callable(**kwargs)]
return f
render_if: Conditional Parts in Nevow Templates
This Nevow renderer has saved me a lot of time:
def render_if(self, ctx, data):
r=ctx.tag.allPatterns(str(bool(data)))
return ctx.tag.clear()[r]
Use it like this:
<nevow:invisible nevow:render="if" nevow:data="items">
<ul nevow:pattern="True"
nevow:render="sequence">
<li nevow:pattern="header">The items are a-coming!</li>
<li nevow:pattern="item">(the items will be here)</li>
</ul>
</nevow:invisible>
And now, if the list returned by data_items is empty, there will be no <ul> tag at all in the output.
I just realized non-boolean tests may be wanted -- for example, test if a string matches a regexp. You could do that by mangling the data before render_if, but that's not nice, because then you don't have access to the original data inside nevow:pattern="True". So, instead let's parametrize the test:
def render_ifparam(self, name):
tester = getattr(self, 'tester_%s' % name, None)
if tester is None:
callable = lambda context, data: context.tag[
"The tester named '%s' was not found in %r." % (name, self)]
return callable
def f(ctx, data):
r=ctx.tag.allPatterns(str(bool(tester(data))))
return ctx.tag.clear()[r]
return f
Note how we still cast the return value of the tester to boolean. You could avoid that and call the renderer render_switch. Adding support for Deferred tests would be quite easy, too. The only ugly part is I don't know of any way to make the same renderer work nicely for nevow:render="if" and nevow:render="ifparam foo".
[Updated to add return f, also renamed second render_if to render_ifparam to clarify things a bit. Thanks k3mper.]