wiki:PresentationTier

Presentation tier

This document describes how to generate HTML and XML dialects with the service offered by the framework.

The document ComponentModel explains how to associate HTML or XML views to the components.

Principles

With Nagare, to generate a HTML text, you always build a tree of objects in memory, a Document Objects Model (not the DOM version of the W3C), that you can serialize in a (X)HTML text.

One of the benefic effect of this serialization is that the generated HTML or XHTML is always valid.

The framework offers to you three ways to generate the DOM:

  1. functional
  2. imperative
  3. templating

It's important to note that these three methods all generate a DOM and so can be freely mixed to build an aggregating DOM.

DOM objects creation

A Renderer is a DOM objects factory. For example, the XHTML renderer namespaces/xhtml.py#xhtml.Renderer has an attribute, for each XHTML tag, that create a DOM object:

>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()
>>> h.div
<Element div at 8410b6c>

DOM objects API

ElementTree API

A DOM object is an instance of the class _Tag from namespaces/xml.py#xml._Tag. As this class inherits from lxml.etree.ElementBase, the DOM objects offer an ElementTree API.:

>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()

>>> root = h.div
>>> root.set('id', 'content')

>>> title = h.h1
>>> title.text = 'Hello world'

>>> root.append(title)
>>> root.write_htmlstring()
'<div id="content"><h1>Hello world</h1></div>'

Lxml API

You can also validate a DOM tree against a DTD, XML Schema, RelaxNG or Schematron document.

And you can use XPath and Xsl transformations.

Functional DOM building API

The _Tag class implements a __call__ method to add children, text or attributes to a DOM object:

def __call__(self, *children, **attributes)

Each element in children can be a:

  • DOM object that is added as a child of the DOM object
  • string, unicode string, integer or float that is converted to unicode string and added as data to the DOM object
  • list, tuple or generator which each elements are added to the DOM object
  • dictionary which each items are added as attributes of the DOM object

Each items in attributes are added as attributes of the DOM object.

Note

As a keyword parameter cannot have the name of a Python reserved keyword, add a _ character to the name (i.e: h.div(class_='content'))

Using the DOM object creation API of the renderers, with the nesting of calls to the created DOM objects, a DOM tree can be built in a functional way:

>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()

>>> root = h.div(h.h1('Hello world'), id='content')
>>> root.write_htmlstring()
<div id="content"><h1>Hello world</h1></div>'

Imperative DOM building API

A DOM object is also a context manager

Once a context is opened using the with statement, children can be added to the context manager using the << operator on the renderer.

So a DOM tree can be imperatively built from the nesting of contexts. With is root finally retrieved from the attribute root of the Renderer:

>>> from __future__ import with_statement

>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()

>>> with h.div:
...     h << { 'id' : 'content' }
...     with h.h1:
...         h << 'Hello world'

>>> root = h.root
>>> root.write_htmlstring()
'<div id="content"><h1>Hello world</h1></div>'

Note

Dont't forget the line from __future__ import with_statement at the beginning of the file to activate the with statement in Python 2.5.

In a component view, don't forget to return the tree with h.root

Remember that the different ways to build a DOM tree can be freely mixed, so the example above can be rewrote as:

>>> from __future__ import with_statement

>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()

>>> with h.div(id='content'):
...     h << h.h1('Hello world')

>>> root = h.root
>>> root.write_htmlstring()
'<div id="content"><h1>Hello world</h1></div>'

Or, creating a HTML list from the Python list l:

# Functional
>>> root = h.ul([u.li(element) for element in l])

# Imperative
>>> with h.ul:
...     for element in l:
...         h << h.ul(element)
>>> root = h.root

Template

The templating system in Nagare follows the idea of the meld family: the designer who creates the XHTML template only gives identifiers to nodes. Then the developer can parse the template into a DOM tree, search for the DOM objects given their identifiers and manipulates them in Python.

1. Creating the template

The template must be a valid XML document, for example a XHTML template and the namespace http://www.plope.com/software/meld3 must be declared.

To mark the nodes, use the attribute id of this namespace:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:meld="http://www.plope.com/software/meld3">
    <body>
        <h1 meld:id="title">Hello</h1>
    </body>
</html>

The helper method meld_id() is available to add, in Python, a meld:id attribute to a DOM object:

>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()

>>> tree = h.html(
...            h.body(
...                h.h1("Hello").meld_id("title")
...            )
...        )
>>> print tree.write_xmlstring(pretty_print=True)
<html>
  <body>
    <h1 xmlns:ns0="http://www.plope.com/software/meld3" ns0:id="title">Hello</h1>
  </body>
</html>

2. Parsing the template

You can use the parse_htmlstring() method of a xhtml renderer to parse a template:

>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()

>>> root = h.parse_html('/tmp/template.xml', xhtml=True)

>>> print root.write_xmlstring()
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:meld="http://www.plope.com/software/meld3">
    <body>
        <h1 meld:id="title">Hello</h1>
    </body>
</html>

3. Finding the DOM objects

Use the findmeld() method of the DOM objects to retreive the marked nodes:

>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()

>>> root = h.parse_html('/tmp/template.xml', xhtml=True)

>>> root.findmeld('title')
<Element {http://www.w3.org/1999/xhtml}h1 at 8410c5c>

4. Manipulating the DOM objects

Once a DOM object is found, you can manipulate it with the normal ElementTree, Lxml, functional or imperative API:

>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()

>>> root = h.parse_html('/tmp/template.xml', xhtml=True)

>>> t = root.findmeld('title')
>>> t.text = 'World'

>>> print root.write_xmlstring()
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:meld="http://www.plope.com/software/meld3">
    <body>
        <h1 meld:id="title">World</h1>
    </body>
</html>

These three methods of the DOM objects are helpers to work with templates:

  • fill(self, *children, **attributes) -- replaces the children of the DOM object by the given children and appends the attributes to the DOM object current attributes.

    >>> from nagare.namespaces import xhtml
    >>> h = xhtml.Renderer()
    
    >>> root = h.div(h.h1('Hello world'), id='content')
    >>> root.write_htmlstring()
    <div id="content"><h1>Hello world</h1></div>'
    
    >>> root.fill("Go to ", h.a("Nagare home", href="http://www.nagare.org"), class_="description")
    
    >>> root.write_htmlstring()
     '<div id="content" class="description">Go to <a href="http://www.nagare.org">Nagare home</a></div>'
    
  • replace(self, *children) -- replace the DOM object by the given children:

    >>> from nagare.namespaces import xhtml
    >>> h = xhtml.Renderer()
    
    >>> root = h.div(h.h1('Hello world'), id='content')
    >>> root.write_htmlstring()
    <div id="content"><h1>Hello world</h1></div>'
    >>> root[0].write_htmlstring()
    <h1>Hello world</h1>
    
    >>> root[0].replace(h.h2('Nagare'))
    
    >>> root.write_htmlstring()
    '<div id="content"><h2>Nagare</h2></div>'
    
  • repeat(self, iterable, childname=None) -- (description taken from the meld3 site)

    Repeats an element with values from an iterable.

    If childname is not None, repeat the element on which repeat was called, otherwise find the child element with a meld:id matching childname and repeat that. The element is repeated within its parent element.

    This method returns an iterable; the value of each iteration is a two-sequence in the form (newelement, data). newelement is a clone of the template element (including clones of its children) which has already been seated in its parent element in the template. data is a value from the passed in iterable. Changing newelement (typically based on values from data) mutates the element "in place".

    >>> from nagare.namespaces import xhtml
    >>> h = xhtml.Renderer()
    
    >>> root = h.parse_xmlstring('''<ul xmlns:meld="http://www.plope.com/software/meld3">
    ...   <li meld:id="alist" align="center">A line example</li>
    ... </ul>''')
    
    >>> items = ['Item %d' % i for i in range(3)]
    
    >>> for (element, item) in root.findmeld('alist').repeat(items):
    ...     element.text = item
    ...
    
    >>> print root.write_xmlstring()
    <ul xmlns:meld="http://www.plope.com/software/meld3">
    <li align="center">Item 0</li>
    <li align="center">Item 1</li>
    <li align="center">Item 2</li>
    </ul>
    

DOM tree serialization

Once a tree of DOM object is build, it can be serialized in (X)HTML:

  • write_xmlstring(self, encoding='utf-8', pipeline=True) -- serializes the DOM tree into a XML string, encoded according to the encoding parameter. If pipeline is True, the eventual meld:id attributes are removed, else they are kept so that the xml string can be re-parse as a template.
  • write_htmlstring(self, encoding='utf-8', pipeline=True) -- serializes the DOM tree into a HTML string, encoded according to the encoding parameter. If pipeline is True, the eventual meld:id attributes are removed.