Previous section   Next section

Practical Programming in Tcl & Tk, Third Edition
By Brent B. Welch

Table of Contents
Chapter 18.  TclHttpd Web Server

HTML + Tcl Templates

The template system uses HTML pages that embed Tcl commands and Tcl variable references. The server replaces these using the subst command and returns the results. The server comes with a general template system, but using subst is so easy you could create your own template system. The general template framework has these components:

  • Each .html file has a corresponding .tml template file. This feature is enabled with the Doc_CheckTemplates command in the server's configuration file. Normally, the server returns the .html file unless the corresponding .tml file has been modified more recently. In this case, the server processes the template, caches the result in the .html file, and returns the result.

  • A dynamic template (e.g., a form handler) must be processed each time it is requested. If you put the Doc_Dynamic command into your page, it turns off the caching of the result in the .html page. The server responds to a request for a .html page by processing the .tml page. Or you can just reference the .tml file directly. If you do this, the server always processes the template.

  • The server creates a page global Tcl variable that has context about the page being processed. Table 18-7 lists the elements of the page array.

  • The server initializes the env global Tcl variable with similar information, but in the standard way for CGI scripts. Table 18-8 lists the elements of the env array that are set by Cgi_SetEnv in cgi.tcl.

  • The server supports per-directory ".tml" files that contain Tcl source code. These files are designed to contain procedure definitions and variable settings that are shared among pages. The name of the file is simply ".tml", with nothing before the period. This is a standard way to hide files in UNIX, but it can be confusing to talk about the per-directory ".tml" files and the file.tml templates that correspond to file.html pages. The server will source the ".tml" files in all directories leading down to the directory containing the template file. The server compares the modify time of these files against the template file and will process the template if these ".tml" files are newer than the cached .html file. So, by modifying the ".tml" file in the root of your URL hierarchy, you invalidate all the cached .html files.

  • The server supports a script library for the procedures called from templates. The Doc_TemplateLibrary procedure registers this directory. The server adds the directory to its auto_path, which assumes you have a tclIndex or pkgIndex.tcl file in the directory so that the procedures are loaded when needed.

Where to put your Tcl Code

There are three places you can put the code of your application: directly in your template pages, in the per-directory ".tml" files, or in the library directory.

The advantage of putting procedure definitions in the library is that they are defined one time but executed many times. This works well with the Tcl byte-code compiler. The disadvantage is that if you modify procedures in these files, you have to explicitly source them into the server for these changes to take effect. The /debug/source URL described on page 267 is handy for this chore.

The advantage of putting code into the per-directory ".tml" files is that changes are picked up immediately with no effort on your part. The server automatically checks if these files are modified and sources them each time it processes your templates. However, that code is run only one time, so the byte-code compiler just adds overhead.

I try to put as little code as possible in my file.tml template files. It is awkward to put lots of code there, and you cannot share procedures and variable definitions easily with other pages. Instead, my goal is to have just procedure calls in the template files, and put the procedure definitions elsewhere. I also avoid putting if and foreach commands directly into the page.

Templates for Site Structure

The next few examples show a simple template system used to maintain a common look at feel across the pages of a site. Example 18-5 shows a simple one-level site definition that is kept in the root .tml file. This structure lists the title and URL of each page in the site:

Example 18-5 A one-level site structure.
set site(pages) {
   Home                /index.html
   "Ordering Computers"/ordering.html
   "New Machine Setup" /setup.html
   "Adding a New User" /newuser.html
   "Network Addresses" /network.html

Each page includes two commands, SitePage and SiteFooter, that generate HTML for the navigational part of the page. Between these commands is regular HTML for the page content. Example 18-6 shows a sample template file:

Example 18-6 A HTML + Tcl template file.
[SitePage "New Machine Setup"]
This page describes the steps to take when setting up a new
computer in our environment. See
<a href=/ordering.html>Ordering Computers</a>
for instructions on ordering machines.
<li>Unpack and setup the machine.
<li>Use the Network control panel to set the IP address
and hostname.
<!-- Several steps omitted -->
<li>Reboot for the last time.

The SitePage procedure takes the page title as an argument. It generates HTML to implement a standard navigational structure. Example 18-7 has a simple implementation of SitePage:

Example 18-7 SitePage template procedure.
proc SitePage {title} {
   global site
   set html "<html><head><title>$title</title></head>\n"
   append html "<body bgcolor=white text=black>\n"
   append html "<h1>$title</h1>\n"
   set sep ""
   foreach {label url} $site(pages) {
      append html $sep
      if {[string compare $label $title] == 0} {
         append html "$label"
      } else {
         append html "<a href='$url'>$label</a>"
      set sep " | "
   return $html

The foreach loop that computes the simple menu of links turns out to be useful in many places. Example 18-8 splits out the loop and uses it in the Site-Page and SiteFooter procedures. This version of the templates creates a left column for the navigation and a right column for the page content:

Example 18-8 SiteMenu and SiteFooter template procedures.
proc SitePage {title} {
   global site
   set html "<html><head><title>$title</title></head>\n\
      <body bgcolor=$site(bg) text=$site(fg)>\n\
      <!-- Two Column Layout -->\n\
      <table cellpadding=0>\n\
      <!-- Left Column -->\n\
      <img src='$site(mainlogo)'>\n\
      <font size=+1>\n\
      [SiteMenu <br> $site(pages)]\n\
      <!-- Right Column -->\n\
   return $html
proc SiteFooter {} {
   global site
   set html "<p><hr>\n\
      <font size=-1>[SiteMenu | $site(pages)]</font>\n\
   return $html
proc SiteMenu {sep list} {
   global page
   set s ""
   set html ""
   foreach {label url} $list {
      if {[string compare $page(url) $url] == 0} {
         append html $s$label
      } else {
         append html "$s<a href='$url'>$label</a>"
      set s $sep
   return $html

Of course, a real site will have more elaborate graphics and probably a two-level, three-level, or more complex tree structure that describes its structure.You can also define a family of templates so that each page doesn't have to fit the same mold. Once you start using templates, it is fairly easy to change both the template implementation and to move pages around among different sections of your Web site.

There are many other applications for "macros" that make repetitive HTML coding chores easy. Take, for example, the link to /ordering.html in Example 18-6. The proper label for this is already defined in $site(pages), so we could introduce a SiteLink procedure that uses this:

Example 18-9 The SiteLink procedure.
proc SiteLink {label} {
   global site
   array set map $site(pages)
   if {[info exist map($label)]} {
      return "<a href='$map($label)'>$label</a>"
   } else {
      return $label

If your pages embed calls to SiteLink, then you can change the URL associated with the page name by changing the value of site(pages). If this is stored in the top-level ".tml" file, the templates will automatically track the changes.

      Previous section   Next section