[ Team LiB ] Previous Section Next Section

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 TclHttpd template framework has these components:

  • Each page.html can have a corresponding page.tml template file. This feature is enabled with the Doc_CheckTemplates command in the server's configuration file. Normally, the server returns the page.html file unless the corresponding page.tml file has been modified more recently. In this case, the server processes the template with subst, caches the result in the page.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 page.html page. The server responds to a request for a page.html page by processing the page.tml page. Or you can just reference the page.tml file directly and the server will always processes the template.

  • The server creates a page global Tcl variable that has context about the page being processed. Table 18-6 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-7 lists the elements of the env array that are set by Cgi_SetEnv in cgi.tcl.

  • The server initializes the ncgi module so you can use the ncgi procedures to access query data.

  • 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 page.tml templates that correspond to page.html pages. Before processing each page.tml file, 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 page.html file. So, by modifying the .tml file in the root of your URL hierarchy, you invalidate all the cached page.html files.

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. There are pros and cons to each:

  • The library directory is where you should put most of your code. The library directory is specified with the -library command line argument, and the server loads all files in the library upon startup. 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. You can restart the server, or you can use the /debug/source URL described on page 282 to reload source files into the running server.

  • The .tml files are best for variable definitions that you want to share among pages in a directory, or as a staging area for procedures during development. 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, using .tml files tends to scatter your code around the URL tree and can make it harder to maintain.

  • I try to put as little code as possible directly in my page.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 only procedure calls in the template files, and put the procedure definitions elsewhere. If you want control structures in your page, such as if and foreach, you may want to use the version of those commands provided by the html package, as described on page 277.

Templates for Site Structure

The next few examples show a simple template system used to maintain a common "look and feel" across the pages of a site. The key to a successful template system is a data structure that defines the structure of the site, and some procedures that generate standard navigational HTML structure for your pages. Once you do this, then you can easily add new pages by updating your data structure. The template procedures automatically reformat your site to include the new pages. Example 18-6 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-6 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
}

Of course, your Web site is likely to have more pages and a more elaborate structure. For example, you might have several main sections, each with a collection of pages, or even a three-level hierarchy of pages. Example 18-7 shows another simple data structure to define a two-level structure. The site(sections) variable stores the names and URLs of the main sections. For each section, there is an element of site that lists the pages in that section. Only the About section is shown in the example:

Example 18-7 A two-level site structure
set site(sections) {
   About     /about
   Products  /products
   Support   /support
}
set site(About) {
   Company   company.html
   Contacts  contacts.html
   Directions   directions.html
}

In practice, you may want to include more information in your data structure to help you generate HTML. For example, if you have graphics for the main sections, you may need to record their size. Whatever you need, collect it into your data structures and then generate the HTML from procedures. You can quickly give your whole site a face lift with new graphics by changing the template procedures that generate your pages. In contrast, if you hand-code all your pages, it can take months instead of days.

Example 18-8 shows a sample template file for the one-level structure shown in Example 18-6. 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-8 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
[SiteLink "Ordering Computers"]
for instructions on ordering machines.
<ol>
<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.
</ol>
[SiteFooter]

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

Example 18-9 SitePage template procedure, version 1
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-10 splits out the loop and uses it in a new version of SitePage along with the SiteFooter procedure. This version of the templates creates a left column for the navigation and a right column for the page content. The example also puts a few more visual elements (e.g., page background color) into the site array so you can easily maintain them:

Example 18-10 SiteMenu and SiteFooter template procedures
array set site {
   bg        white
   fg        black
   mainlogo  /images/mainLogo.gif
}
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\
      <tr><td>\n\
      <!-- Left Column -->\n\
      <img src='$site(mainlogo)'>\n\
      <font size=+1>\n\
      [SiteMenu <br> $site(pages)]\n\
      </font>\n\
      </td><td>\n\
      <!-- Right Column -->\n\
      <h1>$title</h1>\n\
      <p>\n"
   return $html
}
proc SiteFooter {} {
   global site
   set html "<p><hr>\n\
      <font size=-1>[SiteMenu | $site(pages)]</font>\n\
      <!-- Close Right Column -->\n\
      </td></tr></table>\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
}

There are many other applications for "macros" that make repetitive HTML coding chores easy. For example, take the SiteLink procedure call in Example 18-8. Instead of hand-coding the <A> tag with the link to /ordering.html, the page uses the SiteLink procedure to format the link with a consistent label for the link. Using the procedure also means that the page will automatically get updated if you change the URL associated with the ordering page by modifying site(pages). Example 18-11 shows SiteLink:

Example 18-11 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
   }
}

Using Variables for Important Site Information

Another useful feature of templates is the ability to embed variable references in your pages. Instead of hard coding the sales phone number, or the current product version number, or even the product name, you can put variables into your pages. For example, SiteLink and SitePage take a parameter that is the page title. Instead of hard coding your page titles, you could keep all of your page titles in an array, and use array references everywhere. That puts all the text in one place and makes it easy to change. The array definition would look something like this:

array set title {
    Home Home
    Order "Ordering Computers"
    Setup "New Machine Setup"
    AddUser "Adding a New User"
    Network "Network Addresses"
}

And the calls to SitePage or SiteLink could be made like this:

[SitePage $title(Order)]

The .tml pages are a good place to define the variables because the definitions are shared by all pages in that directory, and in any subdirectories. Also, the definitions in the per-directory .tml override any definitions that come from the top-level .tml file at the root of your URL tree. Changing the definition of the variable in the .tml file immediately updates all the pages that share it.

The main drawback to variable references is the clash with $ in pricing. If you put $10 into a page.tml file, it will raise an error (unless the variable 10 is defined). It turns out that you want to generate prices from some database anyway, so you should avoid hard coding prices into your pages anyway. It is much better to put [price T-shirt] or $price(T-shirt) into your page than $10, although if you must do that, just quote the $ with a backslash, \$10.

    [ Team LiB ] Previous Section Next Section