Edgewall Software

Changes between Initial Version and Version 1 of TracDev/HtmlTemplates


Ignore:
Timestamp:
Feb 25, 2016, 1:07:43 AM (8 years ago)
Author:
Christian Boos
Comment:

extracted from Proposals/Jinja@17

Legend:

Unmodified
Added
Removed
Modified
  • TracDev/HtmlTemplates

    v1 v1  
     1= Architecture of the HTML templates
     2
     3Here we explain the current template page architecture in Trac which is used since the adoption of the Genshi template engine, i.e. since Trac 0.11.
     4
     5We also describe the future template page architecture which will be used when we switch to the Jinja2 template engine (see Proposals/Jinja).
     6For now this architecture closely follows the Genshi one.
     7
     8== Genshi architecture
     9
     10The Genshi template page architecture in Trac follows two key ideas:
     11 - all pages focus on providing the content which is unique to them, and the common parts and look and feel is obtained by //including// a "base" template, //layout.html//
     12 - the layout.html includes a dynamic template which takes care of the common look and feel and can be substituted through the configuration; this is how "themes" are handled, and the default dynamic inclusion is the //theme.html// template
     13
     14
     15I've never really tried the TH:ThemeEnginePlugin plugin, or alternatives, so I can't be sure if I got it right, but from what I can see in Trac's code base itself, the idea with Genshi-based themeing (and page architecture in general) was to have a dynamically loaded "theme" template page that would primarily be in charge of the main structure of all HTML pages.
     16
     17Let's take the example of a simple "end user" page, the search.html page.
     18What happens is that:
     19 - search.html includes
     20   - layout.html, which includes
     21     - $chrome.theme (typically "theme.html", the default theme page that ships with Trac)
     22
     23In more details, the
     24[source:tags/trac-1.0.9/trac/search/templates/search.html search.html] is structured like this:
     25{{{#!html+genshi
     26<html>
     27  <xi:include href="layout.html" />
     28  <head>
     29    <title>Search</title>
     30    ...
     31  </head>
     32  <body>
     33    <div id="content" class="search">
     34      <h1>Search</h1>
     35      ...
     36    </div>
     37  </body>
     38</html>
     39}}}
     40 - it starts by including the layout.html page (`<xi:include href="layout.html" />`)
     41 - it then provides the `<head>` and `<body>` elements specific to that search page;
     42   these elements will be processed by the `<py:match>`es filters defined so far,
     43   in the order in which they have been included
     44
     45The [source:tags/trac-1.0.9/trac/templates/layout.html  layout.html]
     46page in turn is structured like this:
     47{{{#!html+genshi
     48<html>
     49  <py:match path="head"><head>
     50    <title py:with="title = list(select('title/text()'))">
     51      ${title} – ${project.name or 'Trac'}  <!-- e.g.  "Search - Trac" -->
     52    </title>
     53    ...
     54    ${select("*[local-name() != 'title']|text()|comment()")}
     55  </head></py:match>
     56
     57  <py:match path="body"><body>
     58
     59    ${select('*|text()|comment()')}         <!-- e.g. "<h1>Search</h1>"
     60                                                       ... -->
     61    ...
     62    <div id="altlinks" py:if="'alternate' in chrome.links">
     63      ...
     64    </div>
     65  </body></py:match>
     66
     67  <xi:include href="$chrome.theme"><xi:fallback /></xi:include>
     68</html>     
     69}}}
     70 - it defines `<py:match>` filters for transforming the content provided
     71   in `<head>` and `<body>` elements in the including template (search.html);
     72   the head filter adds some content to the `<title>` and prepends some content
     73   in the <head>, the body filter appends some content in  the <body>
     74 - it then **dynamically** includes some "theme" page, `$chrome.theme`
     75
     76By default, this theme page will be our
     77[source:tags/trac-1.0.9/trac/templates/theme.html theme.html] template:
     78{{{#!xml
     79<html>
     80  <body>
     81    <div id="banner">
     82      ...
     83    </div>
     84    <div id="main">
     85      ...
     86      ${select('*|text()|comment()')}       <!-- e.g. "<h1>Search</h1>"
     87                                                       ...
     88                                                       <div id="altlinks" ... </div> -->
     89    </div>
     90    <div id="footer">
     91      ...
     92    </div>
     93  </body>
     94</html>
     95}}}
     96 - it defines a `<py:match>` filter on the `<body>` tag
     97   (the one that will be produced by the previously applied filters,
     98   i.e. the output of the `<py:match>` from the layout.html)
     99   and it will **embed** that element into some predefined HTML
     100   structure (inside a div element), and
     101   it will prepend and append other divs around it:
     102
     103So this dynamic theme template has the last say, and can theoretically re-order
     104the content generated by the previous filters any way it likes, although in practice
     105it simply inserts the body content produced by previous steps inside a predefined
     106structure (the `<div id="main">`).
     107
     108An example of another theme page: [https://github.com/chevah/trac-bootstrap-theme/blob/master/templates/theme.html trac-bootstrap-theme's theme.html].
     109
     110
     111
     112Note that this scheme can be extended to additional intermediate levels,
     113for example see what we do with the admin panels.
     114
     115One such panel is
     116[source:tags/trac-1.0.9/trac/admin/templates/admin_basics.html admin_basics.html]:
     117{{{#!html+genshi
     118<html>
     119  <xi:include href="admin.html" />
     120  <head>
     121    <title>Basics</title>
     122  </head>
     123
     124  <body>
     125    ...
     126  </body>
     127</html>
     128}}}
     129 - it starts by including the admin.html page
     130 - then it provides the `<head>` and `<body>` elements specific to that panel
     131   
     132The [source:tags/trac-1.0.9/trac/admin/templates/admin.html admin.html] page
     133is similar to the search.html page in that it includes the layout.html, but
     134it also first contains its own `<py:match>` templates to organize the content of
     135the admin panel which included it:
     136{{{#!html+genshi
     137<html>
     138  <py:match path="head" once="true"><head>
     139    <title>Administration: ${select('title/text()')}</title>
     140    ${select("*[local-name() != 'title']")}
     141  </head></py:match>
     142
     143  <py:match path="body" once="true" buffer="false"><body>
     144    <div id="content" class="admin">
     145      <h1>Administration</h1>
     146      <div id="tabs">
     147      <div id="tabcontent">
     148        ${select("*|text()")}
     149        <br style="clear: right" />
     150      </div>
     151    </div>
     152
     153  </body></py:match>
     154
     155  <xi:include href="layout.html" />
     156</html>
     157}}}
     158 - defines `<py:match>` filters for `<head>` and `<body>` (of the including panel page),
     159   which in turn will produce modified `<head>` and `<body>` elements
     160 - it then includes the layout.html page (see above)
     161
     162Feel free to brush up your Genshi craft by reading ([G:GenshiTutorial#AddingaLayoutTemplate]),
     163as I just did ;-)
     164
     165
     166== Jinja2 architecture
     167
     168Jinja2 can do dynamic includes as well, or more precisely, dynamic ''extends''.
     169Therefore the Genshi approach can be transposed to Jinja: have the end user page extend the layout page,
     170then have the layout page extend whatever has been defined to be the theme page.
     171
     172The differences with Genshi are subtle: while in both case the control of the
     173output is delegated to the more generic page, with Jinja2 the parent only controls
     174what it puts around //blocks//. It can put some default content in these blocks,
     175but the end user page has the final say about what to do with this default content,
     176as it can reuse it inside its block (by calling `super()`) or not.
     177
     178Let's transpose the previous example of the search.html template.
     179For as long as we'll have both template engines coexisting,
     180we'll prefix the new Jinja2 templates with a `j`.
     181So we're now discussing:
     182 - jsearch.html, which extends
     183   - jlayout.html, which extends
     184     - jtheme.html (as this is our default theme page)
     185
     186In more details, we describe what happens with the
     187[source:cboos.git/trac/search/templates/jsearch.html jsearch.html] page:
     188{{{#!html+jinja
     189# extends 'jlayout.html'
     190
     191<!DOCTYPE html>
     192<html>
     193  <head>
     194    <title>
     195    #   block title
     196    ${_("Search")}    ${ super() }
     197    #   endblock title
     198    </title>
     199
     200    # block head
     201    ${ super() }
     202    ...
     203    # endblock head
     204  </head>
     205
     206  <body>
     207    # block content
     208    <div id="content" class="search">
     209      <h1>${_("Search")}</h1>
     210      ...
     211    </div>
     212  </body>
     213</html>
     214}}}
     215 - it starts by //extending// the jlayout.html page (`# extends 'jlayout.html')
     216 - then it redefines the ''title'', ''head'' and ''content'' blocks,
     217   and has to place a `${ super() }` expression in order to insert the default
     218   content proposed by the extended template at the right place;
     219   note that the presence of the `<html>`, `<head>` and `<body>` tags here
     220   is strictly "decorative", it will be ignored in the final output. As we're
     221   in a template extending another, only what's in the redefined blocks matters.
     222
     223These blocks are first defined in the extended template, jlayout.html.
     224
     225The [source:cboos.git/trac/templates/jlayout.html jlayout.html] page looks like this:
     226{{{#!html+jinja
     227# extends ('j' + chrome.theme)
     228
     229<!DOCTYPE html>
     230<html>
     231  <head>
     232    # block head
     233
     234    <title>
     235    #  block title
     236    – ${project.name or 'Trac'}
     237    #  endblock title
     238    </title>
     239
     240    ...
     241    # endblock head
     242  </head>
     243
     244  <body>
     245    # block content
     246    # endblock content
     247
     248    ...
     249    <div id="altlinks">
     250      ...
     251    </div>
     252  </body>
     253</html>
     254}}}
     255 - first **dynamically** //extends// in turn some "theme" page (`# extends ('j' + chrome.theme)`)
     256 - then the jlayout.html template defines a few blocks:
     257    - the ''head'' block and its ''title'' sub-block; here we understand why we've put
     258      the ''title'' block **outside** of the ''head'' block in the jsearch.html template:
     259      the jlayout.html's ''head'' block contains among other things a `<title>` element,
     260      and we're reusing  that default content in the inheriting ''head'' block; if in
     261      jsearch.html we had defined the <title> element in the ''head'' block as well,
     262      we would have had two of these <title> elements in that block
     263    - the ''content'' block which is filled with some predefined, generic content,
     264      mostly the same stuff that could be found in the corresponding layout.html,
     265      in `<py:match>` filters
     266
     267By default, this theme template will be our
     268[source:cboos.git/trac/templates/jtheme.html jtheme.html] page:
     269{{{#!html+jinja
     270
     271<!DOCTYPE html>
     272<html>
     273  <head>
     274    # block head
     275    # endblock head
     276  </head>
     277
     278  <body>
     279    # block body
     280    <div id="banner">
     281      ...
     282    </div>
     283    <div id="main">
     284      ...
     285      # block content
     286      (here goes the content of the content block produced by layout.html)
     287      # endblock content
     288    </div>
     289    <div id="footer">
     290      ...
     291    </div>
     292    # block body
     293  </body>
     294</html>
     295}}}
     296 - it defines a ''head'' block inside an otherwise empty `<head>` element;
     297   this means this is simply a "slot" that will be filled by the content
     298   of the `head` block in the extending templates (in this case, jlayout.html)
     299 - it contains a `<body>` element;
     300   as we want to replicate what the original theme.html did,
     301   what we want to achieve here is to provide a ''slot''
     302   at the place where we want to insert the content
     303   produced by the jlayout.html template;
     304   I didn't name that inner block "body", as this could be confusing:
     305   we're not in control of the `<body>` element there, just of
     306   a fraction of it, the bottom part of the main div.
     307
     308Note that //if// we wanted to be in full control of the body in the extending
     309template, we could (as opposed to what you can do in Genshi): we would simply
     310have to redefine the ''body'' block which contains all of the default structure
     311(possibly reusing the content of that block by a call to `${ super() }`).
     312In our case, neither jsearch.html nor jlayout.html redefine the `body` block,
     313as they're happy with what jtheme does with it.
     314
     315
     316Depending how one looks at it, it seems this approach is even more flexible than
     317what we had in Genshi, as the end user template can decide which bits of the
     318parent template it wants or not ("bottom-up" control), something that was not
     319readily doable with Genshi ("top-down" control).
     320
     321
     322Like what we did with Genshi, this scheme can be extended to support additional intermediate levels. We did that for the admin and the preference panels.
     323
     324Let's take for example the Logging admin panel, [source:cboos.git/trac/admin/templates/jadmin_logging.html jadmin_logging.html]:
     325{{{#!html+jinja
     326# extends "jadmin.html"
     327<html>
     328  <head>
     329    <title>
     330      # block admintitle
     331      Logging
     332      # endblock admintitle
     333    </title>
     334  </head>
     335
     336  <body>
     337    # block adminpanel
     338    ...
     339    # block adminpanel
     340  </body>
     341</html>
     342}}}
     343 - it starts by extending the jadmin.html page
     344 - then it provides the `<head>` and `<body>` elements specific to that panel,
     345   more precisely the //admintitle// and //adminpanel// blocks (if some JavaScript
     346   or other resources are needed, the //head// could be redefined as well)
     347   
     348The [source:cboos.git/trac/admin/templates/jadmin.html@jinja2 jadmin.html] page
     349is similar to the jsearch.html page in that it extends the jlayout.html:
     350{{{#!html+jinja
     351# extends "jlayout.html"
     352<html>
     353  <head>
     354    <title>
     355      # block title
     356      Administration:
     357      #   block admintitle
     358      #   endblock admintitle
     359      ${ super() }
     360      # endblock
     361    </title>
     362  </head>
     363
     364  <body>
     365    # block content
     366    <div id="content" class="admin">
     367      <h1>Administration</h1>
     368      <div id="tabs">
     369      <div id="tabcontent">
     370        # block adminpanel
     371        # endblock adminpanel
     372        <br style="clear: right" />
     373      </div>
     374    </div>
     375    # endblock content
     376  </body>
     377</html>
     378}}}
     379
     380//See [1df4e05c/cboos.git] for the full conversion of admin.html -> jadmin.html and admin_logging.html -> jadmin_logging.html.//
     381
     382
     383I omitted the discussion of the
     384replacement for the `<xi:include href="site.html>` template
     385(`# include site_head.html` and `# include site_body.html`).