Code

e303a1a245e85e4805f8fa24a7c15fe57849aa8e
[roundup.git] / doc / templating.txt
1 ==========================
2 HTML Templating Mechanisms
3 ==========================
5 :Version: $Revision: 1.13 $
7 Current Situation and Issues
8 ============================
10 Syntax
11 ------
13 Roundup currently uses an element-based HTML-tag-alike templating syntax::
15    <display call="checklist('status')">
17 The templates were initially parsed using recursive regular expression
18 parsing, and since no template tag could encapsulate itself, the parser
19 worked just fine. Then we got the ``<require>`` tag, which could have other
20 ``<require>`` tags inside. This forced us to move towards a more complete
21 parser, using the standard python sgmllib/htmllib parser. The downside of this
22 switch is that constructs of the form::
24    <tr class="row-<display call="plain('status')">">
26 don't parse as we'd hope. We can modify the parser to work, but that doesn't
27 another couple of issues that have arisen:
29 1. the template syntax is not well-formed, and therefore is a pain to parse
30    and doesn't play well with other tools, and
31 2. user requirements generally have to be anticipated and accounted for in
32    templating functions (like ``plain()`` and ``checklist()`` above), and
33    we are therefore artificially restrictive.
35 Arguments for switching templating systems:
37 *Pros*
39   - more flexibility in templating control and content
40   - we can be well-formed
42 *Cons*
44   - installed user base (though they'd have to edit their templates with the
45     next release anyway)
46   - current templating system is pretty trivial, and a more flexible system
47     is likely to be more complex
50 Templates
51 ---------
53 We should also take this opportunity to open up the flexibility of the
54 templates through:
56 1. allowing the instance to define a "page" template, which holds the overall
57    page structure, including header and footer
61 Possible approaches
62 ===================
64 Zope's PageTemplates
65 --------------------
67 Using Zope's PageTemplates seems to be the best approach of the lot.
68 In my opinion, it's the peak of HTML templating technology at present. With
69 appropriate infrastructure, the above two examples would read:
71   <span tal:replace="item/status/checklist">status checklist</span>
73   <tr tal:attributes="class string:row-${item/status/name}">
75 ... which doesn't look that much more complicated... honest...
77 Other fun can be had when you start playing with stuff like:
79   <table>
80    <tr tal:repeat="message item/msg/list">
81     <td tal:define="from message/from">
82      <a href="" tal:attributes="href string:mailto:${from/address}"
83                 tal:content="from/name">mailto link</a>
84     </td>
85     <td tal:content="message/title">subject</td>
86     <td tal:content="message/created">received date</td>
87    </tr>
88   </table>
90 Note: even if we don't switch templating as a whole, this document may be
91 applied to the ZRoundup frontend.
93 PageTemplates in a Nutshell
94 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
96 PageTemplates consist of three technologies:
98 TAL - Template Attribute Language
99   This is the syntax which is woven into the HTML using the ``tal:`` tag
100   attributes. A TAL parser pulls out the TAL commands from the attributes
101   runs them using some expression engine.
103 TALES - TAL Expression Syntax
104   The expression engine used in this case is TALES, which runs the expressions
105   that form the tag attribute values. TALES expressions come in three
106   flavours:
108   Path Expressions - eg. ``item/status/checklist``
109    These are object attribute / item accesses. Roughly speaking, the path
110    ``item/status/checklist`` is broken into parts ``item``, ``status``
111    and ``checklist``. The ``item`` part is the root of the expression.
112    We then look for a ``status`` attribute on ``item``, or failing that, a
113    ``status`` item (as in ``item['status']``). If that
114    fails, the path expression fails. When we get to the end, the object we're
115    left with is evaluated to get a string - methods are called, objects are
116    stringified. Path expressions may have an optional ``path:`` prefix, though
117    they are the default expression type, so it's not necessary.
119   String Expressions - eg. ``string:hello ${user/name}``
120    These expressions are simple string interpolations (though they can be just
121    plain strings with no interpolation if you want. The expression in the
122    ``${ ... }`` is just a path expression as above.
124   Python Expressions - eg. ``python: 1+1``
125    These expressions give the full power of Python. All the "root level"
126    variables are available, so ``python:item.status.checklist()`` would be
127    equivalent to ``item/status/checklist``, assuming that ``checklist`` is
128    a method.
130 PageTemplates
131   The PageTemplates module glues together TAL and TALES.
134 Implementation
135 ~~~~~~~~~~~~~~
137 I'm envisaging an infrastructure layer where each template has the following
138 "root level" (that is, directly accessible in the TALES namespace) variables
139 defined:
141 *klass*
142   The current class of node being displayed as an HTMLClass instance. Name is
143   mangled so it can be used in Python expressions.
145 *item*
146   The current node from the database, if we're viewing a specific node, as an
147   HTMLItem instance. If it doesn't exist, then we're on a new item page.
149 (*classname*)
150   this is one of two things:
152   1. the *item* is also available under its classname, so a *user* node
153      would also be available under the name *user*. This is also an HTMLItem
154      instance.
155   2. if there's no *item* then the current class is available through this
156      name, thus "user/name" and "user/name/menu" will still work - the latter
157      will pull information from the form if it can.
159   this is a dangerous attribute, and may cause us pain the long run (its name
160   may clash with other top-level variables ... it already clashed with the
161   proposed *user* variable). It might be safer to go with just *class* and
162   *item*, actually...
164 *form*
165   The current CGI form information as a mapping of form argument name to value
167 *request*
168   Includes information about the current request, including:
169    - the url
170    - the current index information (``filterspec``, ``filter`` args,
171      ``properties``, etc) parsed out of the form. 
172    - methods for easy filterspec link generation
173    - *user*, the current user node as an HTMLItem instance
175 *instance*
176   The current instance
178 *db*
179   The current open database
181 *config*
182   The current instance config
184 *modules*
185   python modules made available (XXX: not sure what's actually in there tho)
187 Accesses through a class (either through *klass* or *db.<classname>*)::
189     class HTMLClass:
190         def __getattr__(self, attr):
191             ''' return an HTMLItem instance '''
192         def classhelp(self, ...)
193         def list(self, ...)
194         def filter(self):
195             ''' Return a list of items from this class, filtered and sorted
196                 by the current requested filterspec/filter/sort/group args
197             '''
199 Accesses through an *item*::
201     class HTMLItem:
202         def __getattr__(self, attr):
203             ''' return an HTMLItem instance '''
204         def history(self, ...)
205         def remove(self, ...)
207 Note: the above could cause problems if someone wants to have properties
208 called "history" or "remove"...
210 String, Number, Date, Interval HTMLProperty
211  a wrapper object which may be stringified for the current plain() behaviour
212  and has methods emulating all the current display functions, so
213  ``item/name/plain`` would emulate the current ``call="plain()``". Also, 
214  ``python:item.name.plain(name=value)`` would work just fine::
216     class HTMLProperty:
217         def __init__(self, instance, db, ...)
218         def __str__(self):
219             return self.plain()
221     class StringHTMLProperty(HTLProperty):
222         def plain(self, ...)
223         def field(self, ...)
224         def stext(self, ...)
225         def multiline(self, ...)
226         def email(self, ...)
228     class NumberHTMLProperty(HTMLProperty):
229         def plain(self, ...)
230         def field(self, ...)
232     class BooleanHTMLProperty(HTMLProperty):
233         def plain(self, ...)
234         def field(self, ...)
236     class DateHTMLProperty(HTMLProperty):
237         def plain(self, ...)
238         def field(self, ...)
239         def reldate(self, ...)
241     class IntervalHTMLProperty(HTMLProperty):
242         def plain(self, ...)
243         def field(self, ...)
244         def pretty(self, ...)
246 Link HTMLProperty
247  the wrapper object would include the above as well as being able to access
248  the class information. Stringifying the object itself would result in the
249  value from the item being displayed. Accessing attributes of this object
250  would result in the appropriate entry from the class being queried for the
251  property accessed (so item/assignedto/name would look up the user entry
252  identified by the assignedto property on item, and then the name property of
253  that user)::
254     
255     class LinkHTMLProperty(HTMLProperty):
256         ''' Be a HTMLItem too '''
257         def __getattr__(self, attr):
258             ''' return a new HTMLProperty '''
259         def download(self, ...)
260         def checklist(self, ...)
262 Multilink HTMLProperty
263  the wrapper would also be iterable, returning a wrapper object like the Link
264  case for each entry in the multilink::
266     class MultilinkHTMLProperty(HTMLProperty):
267         def __len__(self):
268             ''' length of the multilink '''
269         def __getitem(self, num):
270             ''' return a new HTMLItem '''
271         def checklist(self, ...)
272         def list(self, ...)
273  
274 *request*
275  the request object will handle::
277     class Request:
278         def __init__(self, ...)
279         def filterspec(self, ...)
281 Accesses through the *user* attribute of *request*::
283     class HTMLUser(HTMLItem):
284         def hasPermission(self, ...)
286 (note that the other permission check implemented by the security module may
287  be implemented easily in a tal:condition, so isn't needed here)
289 Template files
290 ~~~~~~~~~~~~~~
292 Each instance will have the opportunity to supply the following templates:
294 page
295  This is the overall page look template, and includes at some point a TAL
296  command that includes the variable "content". This variable causes the actual
297  page content to be generated.
299 [classname].[template type]
300  Templates that have this form are applied to item data. There are three forms
301  of special template types:
303  [classname].index
304   This template is used when the URL specifies only the class, and not a node
305   designator.  It displays a list of [classname] items from the database, and
306   a "filter refinement" form.
307   Would perform a TAL ``repeat`` command using the list supplied by
308   ``class/filter``. This deviates from the current situation in that currently
309   the index template specifies a single row, and the filter part is
310   automatically generated.
312  [classname].item
313   This template is used when the URL specifies an item designator. It's the
314   default template used (when no template is explicitly given). It displays
315   a single item from the database using the *classname* variable (that
316   is, the variable of the same name as the class being displayed. If 
318  These two special template types may be overridden by the :template CGI
319  variable.
321 Note that the "newitem" template doesn't exist any more because the item
322 templates may determine whether the page has an existing item to render. The
323 new item page would be accessed by "/tracker/url/issue?:template=item".
324 The old "filter" template has been subsumed by the index template.
327 Integrating Code
328 ~~~~~~~~~~~~~~~~
330 We will install PageTemplates, TAL and ZTUtils in site-packages. If there is a
331 local Zope installation, it will use its own PageTemplates code (Zope modifies
332 the module search path to give precedence to its own module library).
334 We will then install the trivial MultiMapping and ComputedAttribute modules in
335 the Roundup package, and have some import trickery that determines whether
336 they are required, and if so they will be imported as if they were at the
337 "top level" of the module namespace.
339 New CGI client structure
340 ~~~~~~~~~~~~~~~~~~~~~~~~
342 Handling of a request in the CGI client will take three phases:
344 1. Determine user, pre-set "content" to authorisation page if necessary
345 2. Render main page, with callback to "content"
346 3. Render content - if not pre-set, then determine which content to render
349 Use Cases
350 ~~~~~~~~~
352 Meta/parent bug
353   Can be done with addition to the schema and then the actual parent heirarchy
354   may be displayed with a new template page ":dependencies" or something.
356 Submission wizard
357   Can be done using new templates ":page1", ":page2", etc and some additional
358   actions on the CGI Client class in the instance.