|
1 | | -========================== |
2 | | -HTML Templating Mechanisms |
3 | | -========================== |
| 1 | +========================= |
| 2 | +Roundup Tracker Templates |
| 3 | +========================= |
4 | 4 |
|
5 | | -:Version: $Revision: 1.14 $ |
6 | | - |
7 | | -Current Situation and Issues |
8 | | -============================ |
9 | | - |
10 | | -Syntax |
11 | | ------- |
12 | | - |
13 | | -Roundup currently uses an element-based HTML-tag-alike templating syntax:: |
14 | | - |
15 | | - <display call="checklist('status')"> |
16 | | - |
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:: |
23 | | - |
24 | | - <tr class="row-<display call="plain('status')">"> |
25 | | - |
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: |
28 | | - |
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. |
34 | | - |
35 | | -Arguments for switching templating systems: |
36 | | - |
37 | | -*Pros* |
38 | | - |
39 | | - - more flexibility in templating control and content |
40 | | - - we can be well-formed |
41 | | - |
42 | | -*Cons* |
43 | | - |
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 |
48 | | - |
49 | | - |
50 | | -Templates |
51 | | ---------- |
52 | | - |
53 | | -We should also take this opportunity to open up the flexibility of the |
54 | | -templates through: |
55 | | - |
56 | | -1. allowing the tracker to define a "page" template, which holds the overall |
57 | | - page structure, including header and footer |
58 | | - |
59 | | - |
60 | | - |
61 | | -Possible approaches |
62 | | -=================== |
63 | | - |
64 | | -Zope's PageTemplates |
65 | | --------------------- |
66 | | - |
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: |
70 | | - |
71 | | - <span tal:replace="item/status/checklist">status checklist</span> |
72 | | - |
73 | | - <tr tal:attributes="class string:row-${item/status/name}"> |
74 | | - |
75 | | -... which doesn't look that much more complicated... honest... |
76 | | - |
77 | | -Other fun can be had when you start playing with stuff like: |
78 | | - |
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> |
89 | | - |
90 | | -Note: even if we don't switch templating as a whole, this document may be |
91 | | -applied to the ZRoundup frontend. |
92 | | - |
93 | | -PageTemplates in a Nutshell |
94 | | -~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
95 | | - |
96 | | -PageTemplates consist of three technologies: |
97 | | - |
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. |
102 | | - |
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: |
107 | | - |
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. |
118 | | - |
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. |
123 | | - |
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. |
129 | | - |
130 | | -PageTemplates |
131 | | - The PageTemplates module glues together TAL and TALES. |
132 | | - |
133 | | - |
134 | | -Implementation |
135 | | -~~~~~~~~~~~~~~ |
136 | | - |
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: |
140 | | - |
141 | | -*klass* |
142 | | - The current class of item being displayed as an HTMLClass instance. Name is |
143 | | - mangled so it can be used in Python expressions. |
144 | | - |
145 | | -*item* |
146 | | - The current item from the database, if we're viewing a specific item, as an |
147 | | - HTMLItem instance. If it doesn't exist, then we're on a new item page. |
148 | | - |
149 | | -(*classname*) |
150 | | - this is one of two things: |
151 | | - |
152 | | - 1. the *item* is also available under its classname, so a *user* item |
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. |
158 | | - |
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... |
163 | | - |
164 | | -*form* |
165 | | - The current CGI form information as a mapping of form argument name to value |
166 | | - |
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 item as an HTMLItem instance |
174 | | - |
175 | | -*tracker* |
176 | | - The current tracker |
177 | | - |
178 | | -*db* |
179 | | - The current open database |
180 | | - |
181 | | -*config* |
182 | | - The current instance config |
183 | | - |
184 | | -*modules* |
185 | | - python modules made available (XXX: not sure what's actually in there tho) |
186 | | - |
187 | | -Accesses through a class (either through *klass* or *db.<classname>*):: |
188 | | - |
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 | | - ''' |
198 | | - |
199 | | -Accesses through an *item*:: |
200 | | - |
201 | | - class HTMLItem: |
202 | | - def __getattr__(self, attr): |
203 | | - ''' return an HTMLItem instance ''' |
204 | | - def history(self, ...) |
205 | | - def remove(self, ...) |
206 | | - |
207 | | -Note: the above could cause problems if someone wants to have properties |
208 | | -called "history" or "remove"... |
209 | | - |
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:: |
215 | | - |
216 | | - class HTMLProperty: |
217 | | - def __init__(self, instance, db, ...) |
218 | | - def __str__(self): |
219 | | - return self.plain() |
220 | | - |
221 | | - class StringHTMLProperty(HTLProperty): |
222 | | - def plain(self, ...) |
223 | | - def field(self, ...) |
224 | | - def stext(self, ...) |
225 | | - def multiline(self, ...) |
226 | | - def email(self, ...) |
227 | | - |
228 | | - class NumberHTMLProperty(HTMLProperty): |
229 | | - def plain(self, ...) |
230 | | - def field(self, ...) |
231 | | - |
232 | | - class BooleanHTMLProperty(HTMLProperty): |
233 | | - def plain(self, ...) |
234 | | - def field(self, ...) |
235 | | - |
236 | | - class DateHTMLProperty(HTMLProperty): |
237 | | - def plain(self, ...) |
238 | | - def field(self, ...) |
239 | | - def reldate(self, ...) |
240 | | - |
241 | | - class IntervalHTMLProperty(HTMLProperty): |
242 | | - def plain(self, ...) |
243 | | - def field(self, ...) |
244 | | - def pretty(self, ...) |
245 | | - |
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, ...) |
261 | | - |
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:: |
265 | | - |
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:: |
276 | | - |
277 | | - class Request: |
278 | | - def __init__(self, ...) |
279 | | - def filterspec(self, ...) |
280 | | - |
281 | | -Accesses through the *user* attribute of *request*:: |
282 | | - |
283 | | - class HTMLUser(HTMLItem): |
284 | | - def hasPermission(self, ...) |
285 | | - |
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) |
288 | | - |
289 | | -Template files |
290 | | -~~~~~~~~~~~~~~ |
291 | | - |
292 | | -Each instance will have the opportunity to supply the following templates: |
293 | | - |
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. |
298 | | - |
299 | | -[classname].[template type] |
300 | | - Templates that have this form are applied to item data. There are three forms |
301 | | - of special template types: |
302 | | - |
303 | | - [classname].index |
304 | | - This template is used when the URL specifies only the class, and not an item |
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. |
311 | | - |
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 |
317 | | - |
318 | | - These two special template types may be overridden by the :template CGI |
319 | | - variable. |
320 | | - |
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. |
325 | | - |
326 | | - |
327 | | -Integrating Code |
328 | | -~~~~~~~~~~~~~~~~ |
329 | | - |
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). |
333 | | - |
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. |
338 | | - |
339 | | -New CGI client structure |
340 | | -~~~~~~~~~~~~~~~~~~~~~~~~ |
341 | | - |
342 | | -Handling of a request in the CGI client will take three phases: |
343 | | - |
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 |
347 | | - |
348 | | - |
349 | | -Use Cases |
350 | | -~~~~~~~~~ |
351 | | - |
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. |
355 | | - |
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. |
| 5 | +:Version: $Revision: 1.15 $ |
359 | 6 |
|
0 commit comments