Nevow Object Publishing ======================= In Nevow Object Traversal, we learned about the nevow.inevow.IResource.renderHTTP method, which is the most basic way to send HTML to a browser when using Nevow. However, it is not very convenient (or clean) to generate HTML tags by concatenating strings in Python code. In the Nevow Deployment documentation, we saw that it was possible to render a Hello World page using a nevow.rend.Page subclass and providing a "docFactory":: >>> from nevow import rend, loaders >>> class HelloWorld(rend.Page): ... docFactory = loaders.stan("Hello, world!") ... >>> HelloWorld().renderSynchronously() 'Hello, world!' This example does nothing interesting, but the concept of a loader is important in Nevow. The rend.Page.renderHTTP implementation always starts rendering HTML by loading a template from the docFactory. * `The stan DOM`_ * `Tag instances`_ * `Functions in the DOM`_ * `Accessing query parameters and form post data`_ * `Generators in the DOM`_ * `Methods in the DOM`_ * `Data specials`_ * `Render specials`_ * `Pattern specials`_ * `Slot specials`_ * `Data directives`_ * `Render directives`_ * `Flatteners`_ The stan DOM ------------ Nevow uses a DOM-based approach to rendering HTML. A tree of objects is first constructed in memory by the template loader. This tree is then processed one node at a time, applying functions which transform from various Python types to HTML strings. Nevow uses a nonstandard DOM named "stan". Unlike the W3C DOM, stan is made up of simple python lists, strings, and instances of the nevow.stan.Tag class. During the rendering process, "Flattener" functions convert from rich types to HTML strings. For example, we can load a template made up of some nested lists and Python types, render it, and see what happens:: >>> class PythonTypes(rend.Page): ... docFactory = loaders.stan(["Hello", 1, 1.5, True, ["Goodbye", 3]]) ... >>> PythonTypes().renderSynchronously() 'Hello11.5TrueGoodbye3' Tag instances ------------- So far, we have only rendered simple strings as output. However, the main purpose of Nevow is HTML generation. In the stan DOM, HTML tags are represented by instances of the nevow.stan.Tag class. Tag is a very simple class, whose instances have an "attributes" dictionary and a "children" list. The Tag flattener knows how to recursively flatten attributes and children of the tag. To show you how Tags really work before you layer Nevow's convenience syntax on top, try this horrible example:: >>> from nevow import stan >>> h = stan.Tag('html') >>> d = stan.Tag('div') >>> d.attributes['style'] = 'border: 1px solid black' >>> h.children.append(d) >>> class Tags(rend.Page): ... docFactory = loaders.stan(h) ... >>> Tags().renderSynchronously() '
' So, we see how it is possible to programatically generate HTML by constructing and nesting stan Tag instances. However, it is far more convenient to use the overloaded operators Tag provides to manipulate them. Tag implements a __call__ method which takes any keyword arguments and values and updates the attributes dictionary; it also implements a __getitem__ method which takes whatever is between the square brackets and appends them to the children list. A simple example should clarify things:: >>> class Tags2(rend.Page): ... docFactory = loaders.stan(stan.Tag('html')[stan.Tag('div')(style="border: 1px solid black")]) ... >>> Tags2().renderSynchronously() '' This isn't very easy to read, but luckily we can simplify the example even further by using the nevow.tags module, which is full of "Tag prototypes" for every tag type described by the XHTML 1.0 specification:: >>> class Tags3(rend.Page): ... docFactory = loaders.stan(tags.html[tags.div(style="border: 1px solid black")]) ... >>> Tags3().renderSynchronously() '' Using stan syntax is not the only way to construct template DOM for use by the Nevow rendering process. Nevow also includes loaders.xmlfile which implements a simple tag attribute language similar to the Zope Page Templates (ZPT) Tag Attribute Language (TAL). However, experience with the stan DOM should give you insight into how the Nevow rendering process really works. Rendering a template into HTML in Nevow is really nothing more than iterating a tree of objects and recursively applying "Flattener" functions to objects in this tree, until all HTML has been generated. Functions in the DOM -------------------- So far, all of our examples have generated static HTML pages, which is not terribly interesting when discussing dynamic web applications. Nevow takes a very simple approach to dynamic HTML generation. If you put a Python function reference in the DOM, Nevow will call it when the page is rendered. The return value of the function replaces the function itself in the DOM, and the results are flattened further. This makes it easy to express looping and branching structures in Nevow, because normal Python looping and branching constructs are used to do the job:: >>> def repeat(ctx, data): ... return [tags.div(style="color: %s" % (color, )) ... for color in ['red', 'blue', 'green']] ... >>> class Repeat(rend.Page): ... docFactory = loaders.stan(tags.html[repeat]) ... >>> Repeat().renderSynchronously() '' However, in the example above, the repeat function isn't even necessary, because we could have inlined the list comprehension right where we placed the function reference in the DOM. Things only really become interesting when we begin writing parameterized render functions which cause templates to render differently depending on the input to the web application. The required signature of functions which we can place in the DOM is (ctx, data). The "context" object is essentially opaque for now, and we will learn how to extract useful information out of it later. The "data" object is anything we want it to be, and can change during the rendering of the page. By default, the data object is whatever we pass as the first argument to the Page constructor, **or** the Page instance itself if nothing is passed. Armed with this knowledge, we can create a Page which renders differently depending on the data we pass to the Page constructor:: class Root(page.Page): docFactory = loaders.stan(tags.html[ tags.h1["Welcome."], tags.a(href="foo")["Foo"], tags.a(href="bar")["Bar"], tags.a(href="baz")["Baz"]]) def childFactory(self, ctx, name): return Leaf(name) def greet(ctx, name): return "Hello. You are visiting the ", name, " page." class Leaf(rend.Page): docFactory = loaders.stan(tags.html[greet]) Armed with this knowledge and the information in the Object Traversal documentation, we now have enough information to create dynamic websites with arbitrary URL hierarchies whose pages render dynamically depending on which URL was used to access them. Accessing query parameters and form post data --------------------------------------------- Before we move on to more advanced rendering techniques, let us first examine how one could further customize the rendering of a Page based on the URL query parameters and form post information provided to us by a browser. Recall that URL parameters are expressed in the form:: http://example.com/foo/bar?baz=1&quux=2 And form post data can be generated by providing a form to a browser:: Accessing this information is such a common procedure that Nevow provides a convenience method on the context to do it. Let's examine a simple page whose output can be influenced by the query parameters in the URL used to access it:: def showChoice(ctx, data): choice = ctx.arg('choice') if choice is None: return '' return "You chose ", choice, "." class Custom(rend.Page): docFactory = loaders.stan(tags.html[ tags.a(href="?choice=baz")["Baz"], tags.a(href="?choice=quux")["Quux"], tags.p[showChoice]]) The procedure is exactly the same for simple form post information:: def greet(ctx, data): name = ctx.arg('name') if name is None: return '' return "Greetings, ", name, "!" class Form(rend.Page): docFactory = loaders.stan(tags.html[ tags.form(action="", method="POST")[ tags.input(name="name"), tags.input(type="submit")], greet]) Note that ctx.arg returns only the first argument with the given name. For complex cases where multiple arguments and lists of argument values are required, you can access the request argument dictionary directly using the syntax:: def arguments(ctx, data): args = inevow.IRequest(ctx).args return "Request arguments are: ", str(args) Generators in the DOM --------------------- One common operation when building dynamic pages is iterating a list of data and emitting some HTML for each item. Python generators are well suited for expressing this sort of logic, and code which is written as a python generator can perform tests (if) and loops of various kinds (while, for) and emit a row of html whenever it has enough data to do so. Nevow can handle generators in the DOM just as gracefully as it can handle anything else:: >>> from nevow import rend, loaders, tags >>> def generate(ctx, items): ... for item in items: ... yield tags.div[ item ] ... >>> class List(rend.Page): ... docFactory = loaders.stan(tags.html[ generate ]) ... >>> List(['one', 'two', 'three']).renderSynchronously() 'Aligned left
Aligned center
Aligned right
' Note how the alignment renderer has access to the template node as "ctx.tag". It can examine and change this node, and the return value of the render function replaces the original node in the DOM. Note that here we are returning the template node after changing it. We will see later how we can instead mutate the context and use slots so that the knowledge the renderer requires about the structure of the template is reduced even more. Pattern specials ---------------- When writing render methods, it is easy to inline the construction of Tag instances to generate HTML programatically. However, this creates a template abstraction violation, where part of the HTML which will show up in the final page output is hidden away inside of render methods instead of inside the template. Pattern specials are designed to avoid this problem. A node which has been tagged with a pattern special can then be located and copied by a render method. The render method does not need to know anything about the structure or location of the pattern, only it's name. We can rewrite our previous generator example so that the generator does not have to know what type of tag the template designer would like repeated for each item in the list:: >>> from nevow import rend, loaders, tags, inevow >>> def generate(ctx, items): ... pat = inevow.IQ(ctx).patternGenerator('item') ... for item in items: ... ctx.tag[ pat(data=item) ] ... return ctx.tag ... >>> def string(ctx, item): ... return ctx.tag[ str(item) ] ... >>> class List(rend.Page): ... docFactory = loaders.stan(tags.html[ ... tags.ul(render=generate)[ ... tags.li(pattern="item", render=string)]]) ... >>> List([1, 2, 3]).renderSynchronously() '| Donovan | Preston | Male | California |