view doc/templating.txt @ 960:6b6be8818bdd

more info
author Richard Jones <richard@users.sourceforge.net>
date Tue, 20 Aug 2002 08:09:36 +0000
parents 81d860e61f0b
children 7d68f88f0642
line wrap: on
line source

==========================
HTML Templating Mechanisms
==========================

:Version: $Revision: 1.7 $

Current Situation and Issues
============================

Syntax
------

Roundup currently uses an element-based HTML-tag-alike templating syntax::

   <display call="checklist('status')">

The templates were initially parsed using recursive regular expression
parsing, and since no template tag could be encapsulate itself, the parser
worked just fine. Then we got the ``<require>`` tag, which could have other
``<require>`` tags inside. This forced us to move towards a more complete
parser, using the standard python sgmllib/htmllib parser. The downside of this
switch is that constructs of the form::

   <tr class="row-<display call="plain('status')">">

don't parse as we'd hope. It would be almost impossible to modify the sgmllib
parser to parse the above "correctly", so a wholly new parser would be
required. That is a large undertaking, and doesn't address another couple of
issues that have arisen:

1. the template syntax is not well-formed, and therefore is a pain to parse
   and doesn't play well with other tools, and
2. user requirements generally have to be anticipated and accounted for in
   templating functions (like ``plain()`` and ``checklist()`` above), and
   we are therefore artificially restrictive.

Arguments for switching templating systems:

*Pros*

  - more flexibility in templating control and content
  - we can be well-formed

*Cons*

  - installed user base (though they'd have to edit their templates with the
    next release anyway)
  - current templating system is pretty trivial, and a more flexible system
    is likely to be more complex


Templates
---------

We should also take this opportunity to open up the flexibility of the
templates through:

1. allowing the instance to define a "page" template, which holds the overall
   page structure, including header and footer



Possible approaches
===================

Zope's PageTemplates
--------------------

Using Zope's PageTemplates seems to be the best approach of the lot.
In my opinion, it's the peak of HTML templating technology at present. With
appropriate infrastructure, the above two examples would read:

  <span tal:replace="item/status/checklist">status checklist</span>

  <tr tal:attributes="class string:row-${item/status/name}">

... which doesn't look that much more complicated... honest...

Other fun can be had when you start playing with stuff like:

  <table>
   <tr tal:repeat="message item/msg/list">
    <td tal:define="from message/from">
     <a href="" tal:attributes="href string:mailto:${from/address}"
                tal:content="from/name">mailto link</a>
    </td>
    <td tal:content="message/title">subject</td>
    <td tal:content="message/created">received date</td>
   </tr>
  </table>

PageTemplates in a Nutshell
~~~~~~~~~~~~~~~~~~~~~~~~~~~

PageTemplates consist of three technologies:

TAL - Template Attribute Language
  This is the syntax which is woven into the HTML using the ``tal:`` tag
  attributes. A TAL parser pulls out the TAL commands from the attributes
  runs them using some expression engine.

TALES - TAL Expression Language
  The expression engine used in this case is TALES, which runs the expressions
  that form the tag attribute values. TALES expressions come in three
  flavours:

  Path Expressions - eg. ``foo/bar/frozz``
   These are object attribute / item accesses. Roughly speaking, the path
   ``foo/bar/frozz`` is broken into parts ``foo``, ``bar`` and ``frozz``. The
   ``foo`` part is the root of the expression. We then look for a ``bar``
   attribute on foo, or failing that, a bar item (as in foo['bar']). If that
   fails, the path expression fails. When we get to the end, the object we're
   left with is evaluated to get a string - methods are called, objects are
   stringified. Path expressions may have an optional ``path:`` prefix, though
   they are the default expression type, so it's not necessary.

  String Expressions - eg. ``string:hello ${user/name}``
   These expressions are simple string interpolations (though they can be just
   plain strings with no interpolation if you want. The expression in the
   ``${ ... }`` is just a path expression as above.

  Python Expressions - eg. ``python: 1+1``
   These expressions give the full power of Python. All the "root level"
   variables are available, so ``python:foo.bar.frozz()`` might be equivalent
   to ``foo/bar/frozz``, assuming that ``frozz`` is a method.

PageTemplates
  The PageTemplates module glues together TAL and TALES.


Implementation
~~~~~~~~~~~~~~

I'm envisaging an infrastructure layer where each template has the following
"root level" (that is, driectly accessible in the TALES namespace) variables
defined:

*user*
  the current user node as an HTMLItem instance
*class*
  the current class of node being displayed as an HTMLClass instance
*item*
  the current node from the database, if we're viewing a specific node, as an
  HTMLItem instance
(*classname*)
  the current node is also available under its classname, so a *user* node
  would also be available under the name *user*. This is also an HTMLItem
  instance.
*form*
  the current CGI form information as a mapping of form argument name to value
*instance*
  the current instance
*db*
  the current open database
*config*
  the current instance config
*util*
  utility methods
*modules*
  Python modules made available (XXX: not sure what's actually in there tho)

Accesses through a class (either through *class* or *db.<classname>*):

    class HTMLClass:
        def __getattr__(self, attr):
            ''' return an HTMLItem instance '''
        def classhelp(self, ...)
        def list(self, ...)

Accesses through an *item*::

    class HTMLItem:
        def __getattr__(self, attr):
            ''' return an HTMLItem instance '''
        def history(self, ...)
        def classhelp(self, ...)
        def remove(self, ...)

String, Number, Date, Interval HTMLProperty
 a wrapper object which may be stringified for the current plain() behaviour
 and has methods emulating all the current display functions, so
 ``item/name/plain`` would emulate the current ``call="plain()``". Also, 
 ``python:item.name.plain(name=value)`` would work just fine::

    class HTMLProperty:
        def __init__(self, instance, db, ...)
        def __str__(self):
            return self.plain()

    class StringHTMLProperty(HTLProperty):
        def plain(self, ...)
        def field(self, ...)
        def stext(self, ...)
        def multiline(self, ...)
        def email(self, ...)

    class NumberHTMLProperty(HTMLProperty):
        def plain(self, ...)
        def field(self, ...)

    class BooleanHTMLProperty(HTMLProperty):
        def plain(self, ...)
        def field(self, ...)

    class DateHTMLProperty(HTMLProperty):
        def plain(self, ...)
        def field(self, ...)
        def reldate(self, ...)

    class IntervalHTMLProperty(HTMLProperty):
        def plain(self, ...)
        def field(self, ...)

Link HTMLProperty
 the wrapper object would include the above as well as being able to access
 the class information. Stringifying the object itself would result in the
 value from the item being displayed. Accessing attributes of this object
 would result in the appropriate entry from the class being queried for the
 property accessed (so item/assignedto/name would look up the user entry
 identified by the assignedto property on item, and then the name property of
 that user)::
    
    class LinkHTMLProperty(HTMLProperty):
        ''' Be a HTMLItem too '''
        def __getattr__(self, attr):
            ''' return a new HTMLProperty '''
        def download(self, ...)
        def checklist(self, ...)

Multilink HTMLProperty
 the wrapper would also be iterable, returning a wrapper object like the Link
 case for each entry in the multilink::

    class MultilinkHTMLProperty(HTMLProperty):
        def __len__(self):
            ''' length of the multilink '''
        def __getitem(self, num):
            ''' return a new HTMLItem '''
        def checklist(self, ...)
        def list(self, ...)
 
*util*
 the util object will handle::

    class Util:
        def __init__(self, ...)
        def filterspec(self, ...)
        def note(self, ...)
        def submit(self, ...)

Action
======

1. Investigate how PageTemplates would be integrated into Roundup:

   - we could go for a fully-divorced-from-Zope approach, which would involve
     bundling PageTemplates/TAL/ZTUtils in with Roundup, with all the
     Zope-specific bits removed.
   - we could try to coexist with a Zope installation, but there the problem
     would be that Zope includes its own copy of PageTemplates/TAL/ZTUtils and
     we'd be installing a version in site-packages, which would be bad.

   The latter may allow nicer integration with Zope itself, giving Zope
   Roundup users access to acquired information in their templates. We could
   get around that by modifying the ZRoundup interface to use the "real Zope"
   ZPT. Maybe.

2. Implement the Roundup infrastructure described in the `implementation`_
   above.



Roundup Issue Tracker: http://roundup-tracker.org/