1 ==========================
2 HTML Templating Mechanisms
3 ==========================
5 :Version: $Revision: 1.8 $
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 PageTemplates in a Nutshell
93 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
95 PageTemplates consist of three technologies:
97 TAL - Template Attribute Language
98 This is the syntax which is woven into the HTML using the ``tal:`` tag
99 attributes. A TAL parser pulls out the TAL commands from the attributes
100 runs them using some expression engine.
102 TALES - TAL Expression Language
103 The expression engine used in this case is TALES, which runs the expressions
104 that form the tag attribute values. TALES expressions come in three
105 flavours:
107 Path Expressions - eg. ``item/status/checklist``
108 These are object attribute / item accesses. Roughly speaking, the path
109 ``item/status/checklist`` is broken into parts ``item``, ``status``
110 and ``checklist``. The ``item`` part is the root of the expression.
111 We then look for a ``status`` attribute on ``item``, or failing that, a
112 ``status`` item (as in ``item['status']``). If that
113 fails, the path expression fails. When we get to the end, the object we're
114 left with is evaluated to get a string - methods are called, objects are
115 stringified. Path expressions may have an optional ``path:`` prefix, though
116 they are the default expression type, so it's not necessary.
118 String Expressions - eg. ``string:hello ${user/name}``
119 These expressions are simple string interpolations (though they can be just
120 plain strings with no interpolation if you want. The expression in the
121 ``${ ... }`` is just a path expression as above.
123 Python Expressions - eg. ``python: 1+1``
124 These expressions give the full power of Python. All the "root level"
125 variables are available, so ``python:item.status.checklist()`` would be
126 equivalent to ``item/status/checklist``, assuming that ``checklist`` is
127 a method.
129 PageTemplates
130 The PageTemplates module glues together TAL and TALES.
133 Implementation
134 ~~~~~~~~~~~~~~
136 I'm envisaging an infrastructure layer where each template has the following
137 "root level" (that is, driectly accessible in the TALES namespace) variables
138 defined:
140 *user*
141 the current user node as an HTMLItem instance
142 *class*
143 the current class of node being displayed as an HTMLClass instance
144 *item*
145 the current node from the database, if we're viewing a specific node, as an
146 HTMLItem instance
147 (*classname*)
148 the current node is also available under its classname, so a *user* node
149 would also be available under the name *user*. This is also an HTMLItem
150 instance.
151 *form*
152 the current CGI form information as a mapping of form argument name to value
153 *instance*
154 the current instance
155 *db*
156 the current open database
157 *config*
158 the current instance config
159 *util*
160 utility methods
161 *modules*
162 Python modules made available (XXX: not sure what's actually in there tho)
164 Accesses through a class (either through *class* or *db.<classname>*):
166 class HTMLClass:
167 def __getattr__(self, attr):
168 ''' return an HTMLItem instance '''
169 def classhelp(self, ...)
170 def list(self, ...)
172 Accesses through an *item*::
174 class HTMLItem:
175 def __getattr__(self, attr):
176 ''' return an HTMLItem instance '''
177 def history(self, ...)
178 def classhelp(self, ...)
179 def remove(self, ...)
181 String, Number, Date, Interval HTMLProperty
182 a wrapper object which may be stringified for the current plain() behaviour
183 and has methods emulating all the current display functions, so
184 ``item/name/plain`` would emulate the current ``call="plain()``". Also,
185 ``python:item.name.plain(name=value)`` would work just fine::
187 class HTMLProperty:
188 def __init__(self, instance, db, ...)
189 def __str__(self):
190 return self.plain()
192 class StringHTMLProperty(HTLProperty):
193 def plain(self, ...)
194 def field(self, ...)
195 def stext(self, ...)
196 def multiline(self, ...)
197 def email(self, ...)
199 class NumberHTMLProperty(HTMLProperty):
200 def plain(self, ...)
201 def field(self, ...)
203 class BooleanHTMLProperty(HTMLProperty):
204 def plain(self, ...)
205 def field(self, ...)
207 class DateHTMLProperty(HTMLProperty):
208 def plain(self, ...)
209 def field(self, ...)
210 def reldate(self, ...)
212 class IntervalHTMLProperty(HTMLProperty):
213 def plain(self, ...)
214 def field(self, ...)
216 Link HTMLProperty
217 the wrapper object would include the above as well as being able to access
218 the class information. Stringifying the object itself would result in the
219 value from the item being displayed. Accessing attributes of this object
220 would result in the appropriate entry from the class being queried for the
221 property accessed (so item/assignedto/name would look up the user entry
222 identified by the assignedto property on item, and then the name property of
223 that user)::
225 class LinkHTMLProperty(HTMLProperty):
226 ''' Be a HTMLItem too '''
227 def __getattr__(self, attr):
228 ''' return a new HTMLProperty '''
229 def download(self, ...)
230 def checklist(self, ...)
232 Multilink HTMLProperty
233 the wrapper would also be iterable, returning a wrapper object like the Link
234 case for each entry in the multilink::
236 class MultilinkHTMLProperty(HTMLProperty):
237 def __len__(self):
238 ''' length of the multilink '''
239 def __getitem(self, num):
240 ''' return a new HTMLItem '''
241 def checklist(self, ...)
242 def list(self, ...)
244 *util*
245 the util object will handle::
247 class Util:
248 def __init__(self, ...)
249 def filterspec(self, ...)
250 def note(self, ...)
251 def submit(self, ...)
253 Action
254 ======
256 1. Investigate how PageTemplates would be integrated into Roundup:
258 - we could go for a fully-divorced-from-Zope approach, which would involve
259 bundling PageTemplates/TAL/ZTUtils in with Roundup, with all the
260 Zope-specific bits removed.
261 - we could try to coexist with a Zope installation, but there the problem
262 would be that Zope includes its own copy of PageTemplates/TAL/ZTUtils and
263 we'd be installing a version in site-packages, which would be bad.
265 The latter may allow nicer integration with Zope itself, giving Zope
266 Roundup users access to acquired information in their templates. We could
267 get around that by modifying the ZRoundup interface to use the "real Zope"
268 ZPT. Maybe.
270 2. Implement the Roundup infrastructure described in the `implementation`_
271 above.