Render is quite lightweight, as it uses Tcl's "subst" to handle substitutions. Render.tcl is only a few hundred lines of code and can easily be used with Rig.
Basic use
Adding Render-based templating to an application takes just a few lines of Tcl. Here is a simple web server example, extended to use templates:
Rig hook main.Run OnMainRun proc OnMainRun {} { Render setup [Rig home]/site ;# <= added Httpd start 8080 [namespace code WebRequest] } proc WebRequest {obj} { Render dispatch $obj ;# <= added $obj respond [$obj NEXT] ;# <= changed }
The setup call defines the root directory where template files will be searched. Each dispatch call will perform a template search, based on the request's URL. The respond method does the actual templating and returns the generated page.
Components
A Render (and Mason) "component" is an object with a mix of templates and code. Here is a trivial component which generates some text:
<p>Hello World!</p>
By including some Tcl code, the generated page becomes dynamic:
<p>Hello, it is now [clock format [clock seconds]]</p>
Here is a slightly more elaborate component:
<h1>[CALL .title]</h1> <p>Hello, it is now [currentdate]</p> <% DEF .title %> Welcome $user <% variable user $::env(USER) proc currentdate {} { return [clock format [clock seconds]] } %>
This component has three sections:
- all text up to the first <%...%> is used to define the default ".GET" method
- a method called ".title" which is a simple plain-text template in this case
- a <%...%> init section which gets evaluated once when the component is loaded
Note how the .title method has access to the "user" variable defined in the init section. In Render, each component is mapped to a namespace and all methods have automatic access to the variables and arrays present in that namespace. In fact, a "method" is simply a proc, auto-generated from a mix of Tcl and subst calls. So ".title" is a proc in that component's namespace which can be used like any other proc - it just happens to do template expansion, and the result of the call will be the resulting string.
The following component generates some HTML with a bit more text:
<p>Hello</p> <ul> % foreach x $counts { <li>$x - [string toupper $x] - [string totitle $x]</li> % } </ul> <% variable counts [list one! two! three! [clock seconds] {good bye!}] %>
Explanation: lines starting with "%" are plain Tcl. As you can see, these can be inserted between template lines for looping, conditionals, etc.
Components and files
Every component corresponds to a single template file, located in the directory tree specified in the "Render setup" call. With the above configuration, if the component is at "./site/index.html", then URL http://localhost:8080/index.html loads that component, call its default method, and return the result as a page.
Component files are sourced once when accessed, with a namespace created to hold all variables and state associated to that component. This namespace persists from one page request to the next, so variables will retain their value across multiple page refreshes. i.e. the above $user is set once and then re-used.
There is one special case: if the source file is changed, then the component namespace will be deleted and re-constructed from scratch on its next use. This can be extremely effective when developing and making changes to a website. There is rarely a need to restart the web-server, Render handles all reloading.
In Tcl, all components are implemented as child namespaces of the special "::/" namespace, i.e. component "./site/abc/def.html" corresponds to the namespace at ::/::abc::def.html once loaded.
Default components and inheritance
If there is no component file matching the request URL, then Render performs a search for a special component called "default.mav", starting at the requested directory and moving up to parent directories until a match is found. At that point, the default component takes over the rendering / templating process.
There is one other essential mechanism: auto-handlers. Once a component has been identified, and before actual template processing starts, a second search is performed to find files named "auto.mav" in any of the parent directories.
Auto-handlers let you "wrap" components. The most common use-case for this is to define uniform page headers and footers for an entire site, or for sub-sections of a website. Here is an example auto-handler:
<html> <head><title>[CALL .title]</title></head> <body>[NEXT]</body> </html>
If this is placed in "./site/auto.mav", then every page generated will now be extended to have a more complete HTML page structure, including a title.
What happens, in the index.html example, is that the "auto.mav" template is the first one to run. It generates a bit of HTML, then it asks the "index.html" (!) component to provide a title through its ".title" method, then generates a bit more html, then uses the default method in "index.html" to generate the real content of the page, and finally it adds a bit more boilerplate HTML to properly terminate the page.
Note how "auto.mav" supplies both head and tail, and how all HTML elements are properly balanced. There is nothing in Render which requires this balance, but it sure helps to have all methods and pages be more or less "valid" HTML code.
In a way, auto-handlers work from the outside in, providing the outer structure and then "calling" sub-components to generate inner content. This is a general mechanism which actually goes a lot further than that. Auto-handlers can also take over processing and completely change the way sub-components are used and expanded. There are examples in Mason, whereby the "sub-component" is a text file with nothing but an SQL request in it - with a suitable auto-handler, an entire directory of such "pages" can be processed by evaluating the SQL code, and then formatting and wrapping the results to generate a complete html page.
Another option available to components, is to re-define the parent path used to move up when looking for auto- and default handlers. This makes it possible to present a public URL hierarchy which is completely different from the way in which component files are organized on disk. This is not as important in Render as in Mason, because Rig offers other (simpler) ways to re-write request URLs.
Technical notes
The use of namespace search paths and proc imports/exports is quite tricky:
- the ::/ namespace always searches in ::v as well
- the ::v namespace contains some general procs: DEF, NEXT, REQ, etc
- namespaces under ::/ are set up to search all their parents by default
- these parent path searches can be overridden with the INHERIT command
- each request is a separate namespace, which gets deleted once done
- requests are ensembles, no search paths are used, only an export list
- all requests start out with the "delegate" and "respond" methods
- more can be added using that delegate command ("failed", "done", etc)
- on each dispatch all procs defined in ::v get imported in the new request
- delegate methods are lowercase, the general procs in ::v are uppercase
- delegates are set per request and can be different for each one
- only request ensembles export {*}, all modules use {[a-z]*}
- to switch contexts between requests, only $v::current has to be changed
- modules and components are singletons, all state in them exists only once
The reason for this complexity is that it gives automatic access to all general component procs in every request, while also maintaining the full parent search logic needed for components. If requests were placed below the root of the ::/ namespace, the paths in all children would have to be adjusted each time a different request is set as the current one.
Render dispatch obj
Prepare a new request object for proper dispatching. This will perform a search to locate the right component, including all the default.mav and auto.mav logic.
Render setup ...
Setup the rendering system by specifying one or more directories as template roots.
Render substify in ?var?
Utility code to take a template and turn it into a Tcl script. This is used to implement the methods defined in each component. Can also be used stand- alone.