1 ==========================
2 HTML Templating Mechanisms
3 ==========================
5 :Version: $Revision: 1.10 $
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 be 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. It would be almost impossible to modify the sgmllib
27 parser to parse the above "correctly", so a wholly new parser would be
28 required. That is a large undertaking, and doesn't address another couple of
29 issues that have arisen:
31 1. the template syntax is not well-formed, and therefore is a pain to parse
32 and doesn't play well with other tools, and
33 2. user requirements generally have to be anticipated and accounted for in
34 templating functions (like ``plain()`` and ``checklist()`` above), and
35 we are therefore artificially restrictive.
37 Arguments for switching templating systems:
39 *Pros*
41 - more flexibility in templating control and content
42 - we can be well-formed
44 *Cons*
46 - installed user base (though they'd have to edit their templates with the
47 next release anyway)
48 - current templating system is pretty trivial, and a more flexible system
49 is likely to be more complex
52 Templates
53 ---------
55 We should also take this opportunity to open up the flexibility of the
56 templates through:
58 1. allowing the instance to define a "page" template, which holds the overall
59 page structure, including header and footer
63 Possible approaches
64 ===================
66 Zope's PageTemplates
67 --------------------
69 Using Zope's PageTemplates seems to be the best approach of the lot.
70 In my opinion, it's the peak of HTML templating technology at present. With
71 appropriate infrastructure, the above two examples would read:
73 <span tal:replace="item/status/checklist">status checklist</span>
75 <tr tal:attributes="class string:row-${item/status/name}">
77 ... which doesn't look that much more complicated... honest...
79 Other fun can be had when you start playing with stuff like:
81 <table>
82 <tr tal:repeat="message item/msg/list">
83 <td tal:define="from message/from">
84 <a href="" tal:attributes="href string:mailto:${from/address}"
85 tal:content="from/name">mailto link</a>
86 </td>
87 <td tal:content="message/title">subject</td>
88 <td tal:content="message/created">received date</td>
89 </tr>
90 </table>
92 Note: even if we don't switch templating as a whole, this document may be
93 applied to the ZRoundup frontend.
95 PageTemplates in a Nutshell
96 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
98 PageTemplates consist of three technologies:
100 TAL - Template Attribute Language
101 This is the syntax which is woven into the HTML using the ``tal:`` tag
102 attributes. A TAL parser pulls out the TAL commands from the attributes
103 runs them using some expression engine.
105 TALES - TAL Expression Language
106 The expression engine used in this case is TALES, which runs the expressions
107 that form the tag attribute values. TALES expressions come in three
108 flavours:
110 Path Expressions - eg. ``item/status/checklist``
111 These are object attribute / item accesses. Roughly speaking, the path
112 ``item/status/checklist`` is broken into parts ``item``, ``status``
113 and ``checklist``. The ``item`` part is the root of the expression.
114 We then look for a ``status`` attribute on ``item``, or failing that, a
115 ``status`` item (as in ``item['status']``). If that
116 fails, the path expression fails. When we get to the end, the object we're
117 left with is evaluated to get a string - methods are called, objects are
118 stringified. Path expressions may have an optional ``path:`` prefix, though
119 they are the default expression type, so it's not necessary.
121 String Expressions - eg. ``string:hello ${user/name}``
122 These expressions are simple string interpolations (though they can be just
123 plain strings with no interpolation if you want. The expression in the
124 ``${ ... }`` is just a path expression as above.
126 Python Expressions - eg. ``python: 1+1``
127 These expressions give the full power of Python. All the "root level"
128 variables are available, so ``python:item.status.checklist()`` would be
129 equivalent to ``item/status/checklist``, assuming that ``checklist`` is
130 a method.
132 PageTemplates
133 The PageTemplates module glues together TAL and TALES.
136 Implementation
137 ~~~~~~~~~~~~~~
139 I'm envisaging an infrastructure layer where each template has the following
140 "root level" (that is, driectly accessible in the TALES namespace) variables
141 defined:
143 *user*
144 The current user node as an HTMLItem instance
146 *class*
147 The current class of node being displayed as an HTMLClass instance
149 *item*
150 The current node from the database, if we're viewing a specific node, as an
151 HTMLItem instance. If it doesn't exist, then we're on a new item page.
153 (*classname*)
154 this is one of two things:
156 1. the *item* is also available under its classname, so a *user* node
157 would also be available under the name *user*. This is also an HTMLItem
158 instance.
159 2. if there's no *item* then the current class is available through this
160 name, thus "user/name" and "user/name/menu" will still work - the latter
161 will pull information from the form if it can.
163 *form*
164 The current CGI form information as a mapping of form argument name to value
166 *request*
167 Includes information about the current request, including:
168 - the url
169 - the current index information (``filterspec``, ``filter`` args,
170 ``properties``, etc) parsed out of the form.
171 - methods for easy filterspec link generation
173 *instance*
174 The current instance
176 *db*
177 The current open database
179 *config*
180 The current instance config
182 *modules*
183 python modules made available (XXX: not sure what's actually in there tho)
185 Accesses through the *user*::
187 class HTMLUser:
188 def hasPermission(self, ...)
190 (note that the other permission check implemented by the security module may
191 be implemented easily in a tal:condition, so isn't needed here)
193 Accesses through a class (either through *class* or *db.<classname>*):
195 class HTMLClass:
196 def __getattr__(self, attr):
197 ''' return an HTMLItem instance '''
198 def classhelp(self, ...)
199 def list(self, ...)
200 def filter(self):
201 ''' Return a list of items from this class, filtered and sorted
202 by the current requested filterspec/filter/sort/group args
203 '''
205 Accesses through an *item*::
207 class HTMLItem:
208 def __getattr__(self, attr):
209 ''' return an HTMLItem instance '''
210 def history(self, ...)
211 def remove(self, ...)
213 Note: the above could cause problems if someone wants to have properties
214 called "history" or "remove"...
216 String, Number, Date, Interval HTMLProperty
217 a wrapper object which may be stringified for the current plain() behaviour
218 and has methods emulating all the current display functions, so
219 ``item/name/plain`` would emulate the current ``call="plain()``". Also,
220 ``python:item.name.plain(name=value)`` would work just fine::
222 class HTMLProperty:
223 def __init__(self, instance, db, ...)
224 def __str__(self):
225 return self.plain()
227 class StringHTMLProperty(HTLProperty):
228 def plain(self, ...)
229 def field(self, ...)
230 def stext(self, ...)
231 def multiline(self, ...)
232 def email(self, ...)
234 class NumberHTMLProperty(HTMLProperty):
235 def plain(self, ...)
236 def field(self, ...)
238 class BooleanHTMLProperty(HTMLProperty):
239 def plain(self, ...)
240 def field(self, ...)
242 class DateHTMLProperty(HTMLProperty):
243 def plain(self, ...)
244 def field(self, ...)
245 def reldate(self, ...)
247 class IntervalHTMLProperty(HTMLProperty):
248 def plain(self, ...)
249 def field(self, ...)
250 def pretty(self, ...)
252 Link HTMLProperty
253 the wrapper object would include the above as well as being able to access
254 the class information. Stringifying the object itself would result in the
255 value from the item being displayed. Accessing attributes of this object
256 would result in the appropriate entry from the class being queried for the
257 property accessed (so item/assignedto/name would look up the user entry
258 identified by the assignedto property on item, and then the name property of
259 that user)::
261 class LinkHTMLProperty(HTMLProperty):
262 ''' Be a HTMLItem too '''
263 def __getattr__(self, attr):
264 ''' return a new HTMLProperty '''
265 def download(self, ...)
266 def checklist(self, ...)
268 Multilink HTMLProperty
269 the wrapper would also be iterable, returning a wrapper object like the Link
270 case for each entry in the multilink::
272 class MultilinkHTMLProperty(HTMLProperty):
273 def __len__(self):
274 ''' length of the multilink '''
275 def __getitem(self, num):
276 ''' return a new HTMLItem '''
277 def checklist(self, ...)
278 def list(self, ...)
280 *request*
281 the request object will handle::
283 class Request:
284 def __init__(self, ...)
285 def filterspec(self, ...)
287 Template files
288 ~~~~~~~~~~~~~~
290 Each instance will have the opportunity to supply the following templates:
292 page
293 This is the overall page look template, and includes at some point a TAL
294 command that includes the variable "content". This variable causes the actual
295 page content to be generated.
297 *.index
298 Displays a list of items from the database, and a "filter refinement" form.
299 Would perform a TAL ``repeat`` command using the list supplied by
300 ``class/filter``. This deviates from the current situation in that currently
301 the index template specifies a single row, and the filter part is
302 automatically generated.
304 *.item
305 Displays a single item from the database using the *classname* variable (that
306 is, the variable of the same name as the class being displayed. If
308 *.filter
309 Displays a full search form for a class.
311 Note that the newitem template doesn't really apply any more because the item
312 templates may determine whether the page has an existing item to render.
315 Integrating Code
316 ~~~~~~~~~~~~~~~~
318 We will install PageTemplates, TAL and ZTUtils in site-packages. If there is a
319 local Zope installation, it will use its own PageTemplates code (Zope modifies
320 the module search path to give precedence to its own module library).
322 We will then install the trivial MultiMapping and ComputedAttribute modules in
323 the Roundup package, and have some import trickery that determines whether
324 they are required, and if so they will be imported as if they were at the
325 "top level" of the module namespace.