Mercurial > p > roundup > code
comparison doc/templating.txt @ 2132:6535aa11418a
*** empty log message ***
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Fri, 26 Mar 2004 01:10:19 +0000 |
| parents | 43ab730ee194 |
| children |
comparison
equal
deleted
inserted
replaced
| 2131:dc65f4a91433 | 2132:6535aa11418a |
|---|---|
| 1 ========================== | 1 ========================= |
| 2 HTML Templating Mechanisms | 2 Roundup Tracker Templates |
| 3 ========================== | 3 ========================= |
| 4 | 4 |
| 5 :Version: $Revision: 1.14 $ | 5 :Version: $Revision: 1.15 $ |
| 6 | 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. | |
| 359 |
