Autohandlers

In the previous section we wrote a full HTML file in a single template. This is not how you want to write websites, though, normally you would put the header in a file, footer in a file, and include them both in each template—in order to not duplicate the header/footer information.

Sytes provides a better mechanism inspired from Perl's HTML::Mason module (which in turn, if I remember correctly, was inspired from HTML::EmbPerl). Auto-handlers are files that your templates implictly “inherit” from, and where you can add stuff like header/footer, include scripts etc. After over 10 years of Web development, I have yet to see a more powerful technique for writing websites. This very website uses autohandlers a lot, and it's a core concept in Sytes.

Essentially, if you drop a file named “autohandler.syt” in the root directory, then any request to execute a template will instead call the autohandler, providing a function that runs the original template. The autohandler can setup header/footer and execute the original template whenever it's convenient by calling (call-next).

So let's modify the previous syte like this. Let's create a file autohandler.syt in the root directory:

;; -*- html -*-
;; in file: /tmp/syte.foobar/root/autohandler.syt
<html>
  <head>
    <title>???</title>
    <link href="/s/css/style.css" type="text/css" rel="stylesheet" />
  </head>
  <body>
    <!-- page header -->
    <div class="header">
      This is the header
    </div>
    <!-- page body -->
    <div class="body">
;;    This is how we call the original template:
      {(call-next)}
    </div>
    <!-- page footer -->
    <div class="footer">
      This is the footer
    </div>
  </body>
</html>

Now we can simplify a lot our index.syt file:

;; -*- html -*-
;; in file: /tmp/syte.foobar/root/index.syt
<h1>Hello world!</h1>
<p>
  This is your first Syte.
</p>

If you access the page now at http://foobar.local/ you should see pretty much the same result, although our index.syt file is a lot simpler. One little gray area is the fact that we can't include the page title in the <title> tag now, because we don't know it. At the time the autohandler is run, the requested template (index.syt) is still pending execution, therefore what could we display in <title>?

The following version answers this question. Here's the autohandler first:

;; -*- html -*-
;; in file: /tmp/syte.foobar/root/autohandler.syt
{[define CONTENT (call-next)]}
<html>
  <head>
    <title>{(@attr 'TITLE)}</title>
    <link href="/s/css/style.css" type="text/css" rel="stylesheet" />
  </head>
  <body>
    <div class="header">This is the header</div>
    <div class="body">{CONTENT}</div>
    <div class="footer">This is the footer</div>
  </body>
</html>

The above autohandler calls the template first, before getting to output the actual page contents. A function provided by Sytes — @attr — allows you to pass information between templates, so because we have (call-next) early, the 'TITLE attribute will be available when we generate the <title> tag.

The output of the template is saved in a local variable CONTENT, and it'll be inserted into the main output at the right time (i.e. in the DIV with class "body").

Just a side note, here is the definition of @attr if you want to understand how it works:

(defun @attr (name . value)
  (let ((attr (*attributes*)))
    (if value
        (set-hash attr name (car value))
        (get-hash attr name))))

You can notice here that there's a *attributes* global function. That's defined by the system and it returns the value of a Common Lisp dynamic variable which is bound to a fresh hash table on each request. This variable is shared between templates, so whatever you put into it in one place will be visible in another over the course of a single request. The function @attr itself takes one or two arguments. If you pass a single argument, it'll return the value of the specified key from the attributes table. If you pass two arguments it will set the value of that key.

Back to index.syt now, it should become the following in order to pass this information to the autohandler:

;; -*- html -*-
;; in file: /tmp/syte.foobar/root/index.syt
<h1>{(@attr 'TITLE "Hello world")}</h1>
<p>
  This is your first Syte.
</p>

We're using a small trick here, @attr returns the value so it'll be inserted into the <h1>...</h1> tag. Of course, you have total freedom over that, for example you could move the H1 tag into the autohandler if that's the common case for most templates.

So now we separated the header/footer and other HTML boilerplate that we need to include in all pages into a single file (the autohandler). If we need to create a new page now, we can do that easily:

;; -*- html -*-
;; in file: /tmp/syte.foobar/root/newpage.syt
<h1>{(@attr 'TITLE "My second page")}</h1>
<p>
  Lorem ipsum.
</p>

Accessing http://foobar.local/newpage should display it with header and footer.

Subdirectories

If you have a nested directory structure and place autohandlers in subdirectories, the autohandlers are chained. This site, for instance, has a big section in the subdirectory /sytes/. All files inherit from the main autohandler (the one in the root directory), but in /sytes/ we want to display additional information such as the table of contents on the right. The file structure is like this:

  /autohandler.syt
  /sytes/autohandler.syt
  /sytes/...

In the toplevel autohandler we have a section like this to display the table of contents that you can see on the right side:

{(let ((sc (@attr 'SUBCONTENT)))
  (when sc
    {<div class="toc">
      {(reverse sc)}
    </div>}))}

There's no hard-coded text into it, it just looks for the SUBCONTENT attribute and if present it's assumed to be a list of things to display on the right side. The autohandler in /sytes/ in turn contains the following:

; -*- html -*-
{(progn

   (set-title "Sytes — a Common Lisp tool for making websites the easy way")

   (push-breadcrumb "Sytes" ".")
   (fork-me-on-github "https://github.com/mishoo/sytes")

   (push-subcontent {
     <h2>Sytes</h2>
     <p>
       A Common Lisp library for building websites the easy way.
     </p>
     <ul>
       <li>{page-link "./" "Sytes"}</li>
       <li>{page-link "./syntax/" "Template syntax"}
         <ul>
           <li>{page-link "./syntax/raw-mode.syt" "Raw mode"}</li>
           <li>{page-link "./syntax/lisp-mode.syt" "Lisp mode"}</li>
         </ul>
       </li>
       <li>{page-link "./compiler.syt" "Compiler"}</li>
       <li>{page-link "./language/" "Language"}</li>
       <li><p><b><a href="https://github.com/mishoo/sytes">Sytes on Github</a></b></p></li>
     </ul>

     <h2>Tutorial</h2>
     <ul>
       <li>{page-link "./tutorial/install.syt" "Installation"}</li>
       <li>{page-link "./tutorial/hello-world.syt" "Hello world"}</li>
       <li>{page-link "./tutorial/language.syt" "Language"}</li>
       <li>{page-link "./tutorial/autohandlers.syt" "Autohandlers"}</li>
       <li>{page-link "./tutorial/dhandlers.syt" "Dhandlers"}</li>
       <li>{page-link "./tutorial/libraries.syt" "Libraries"}</li>
       <li>{page-link "./tutorial/syntactic-sugar.syt" "Syntactic sugar"}</li>
       <li>{page-link "./tutorial/clinterface.syt" "CL interface"}</li>
       <li>{page-link "./tutorial/deploying.syt" "Deploying"}</li>
     </ul>
   })

   (call-next)

)}

where push-subcontent and page-link (defined in a shared library) look like this:

(defun push-subcontent (html)
  (@attr 'SUBCONTENT
         (cons html (@attr 'SUBCONTENT))))

(defun page-link (url text)
  ;; absurl and sameurl are provided by Sytes
  (define absolute (absurl url))
  (define current (sameurl absolute))
  {<a href="{\absolute}" {(if current {class="current"})}>{\text}</a>})

Avoiding them

In some rare occasions you might want to have a file that's not served through an autohandler. For a concrete example, the URL /robots.txt (on every website) is queried by various search engines. That's the place where you can declare which parts of your website should be visited by robots, and which not. Robots are technically free to ignore it, but that's another story; the story we're talking about now is how to serve a proper robots.txt file while all the requests are served by Sytes through autohandlers.

Since it's a request like any other, even if you do have a file named robots.txt in your root directory it'll be interpreted as a Sytes template and will obey the autohandler if one is present. To prevent it from doing that, you can do the following:

{[progn
   (http/set-content-type "text/plain; charset=UTF-8")
   (@inherit nil)
]}~
User-agent: *
Disallow: /s/

The (@inherit nil) expression tells Sytes that this particular template doesn't want to inherit from any autohandler; and (http/set-content-type "text/plain") tells it to output a plain text content-type rather than the default HTML one.

Fork me on Github