last modified 11th of april 2006
This tutorial is still very much work in progress, but the provided text, code and installation instructions should work with the latest ucw development version, at least with sbcl under linux using the built-in httpd server. Have fun.
table of content:
This text will introduce you in a mild mannered pace to the ways of this as of yet UnCommon Web framework, which is a framework to write web applications as you would write normal applications; e.g. with state. Check ucw's features page to get an impression of its features. Ucw is an open source project. Various people donate code on a regular basis. Check the mailing list for a flavour of the current topics. If you're wondering about the merits and weaknesses of either state or stateless web development check this rather nice one-page email conversation between the creator of ucw and a non-believer. If you want to use ucw on a windows machine, I have got no experience or information on workability, but i would very much like to hear about your experiences.
This introduction wants to introduce UCW in a slow and concise manner. We will start with the installation and an initial setup. Then slowly, through simple examples, the reader will be introduced to the various components. We will delve from the outside in and will perhaps some day hit upon the burning hot core. For the brilliant and the impatient this pace will just be to damn slow. Them I advice to read and watch some other tutorials and documentation. They are:
First off UCW doesn't do version numbers. Some people may remember it did and it did, but it doesn't anymore. Still there are two versions of UCW to choose from: ucw_dev and ucw_public, both held in a Darcs repository. ucw_dev is controlled by Marco Baringer, creator of ucw. Ucw_public is world writable. At the moment the work flow of the project appears to be such that usually patches are sent to the mailing-list, which are applied to ucw_dev. Ucw_public sort of lags behind with occasional massive updates. This doesn't mean it's the stable branch, just that less bugs are introduced and less bugs are fixed. At the moment I would advise to go with ucw_dev which is also the focus of this intro. _public might or might not work with this intro text.
To get _dev or _public, install Darcs on your computer and execute:
darcs get http://common-lisp.net/project/ucw/repos/ucw_dev
or
darcs get http://common-lisp.net/project/ucw/repos/ucw_public
Daily snapshots for ucw_dev are available at http://common-lisp.net/project/ucw/tarballs/ucw_dev-latest.tar.gz
Daily snapshots for ucw_public are available at http://common-lisp.net/project/ucw/tarballs/ucw_public-latest.tar.gz
Now it is time for the dependencies. A word of caution: ucw has it's own versions for a number of libraries so even if you already have the latest version of some package, check this documentation or the readme supplied with the sources. As a rule of thumb, get the latest of everything and the ucw version if supplied.
At the moment UCW requires the following dependencies:
A note on compatibility: Some configurations of implementation and server work better than others. Just report your bugs in the mailinglist, the list is quite responsive. As a pointer, an implementation/server-pair of sbcl or openmcl and mod_lisp seem too see the heaviest testing. The built-in httpd server also works well but isn't fit for production use.
Pick one in the list above.
Someone from Debian (Luca Capello) is working hard to to support ucw out of the box, but he is not quite there yet. Keep an eye on the cl-debian mailing list (UCW status, seeing the light at the end of the tunnel) for the latest developments.
Arnesi is a Common Lisp utility suite. It contains various "bits 'n pieces" of code which were useful while developing other code. It can be found on http://common-lisp.net/project/bese/arnesi.html.
You will need the latest version:
darcs get http://common-lisp.net/project/bese/repos/arnesi_dev/
Daily snapshots are available at http://common-lisp.net/project/bese/tarballs/arnesi_dev-latest.tar.gz
yaclml is a collection of macros and utilities for generating XML/HTML like markup from lisp code. It can be found on http://common-lisp.net/project/bese/yaclml.html.
You will need the latest version:
darcs get http://common-lisp.net/project/bese/repos/yaclml/
Daily snapshots are available at: http://common-lisp.net/project/bese/tarballs/yaclml-latest.tar.gz
Parenscript is a javascript compiler. You will need to get the latest development version:
darcs get http://common-lisp.net/project/ucw/repos/parenscript/
Daily snapshots are available at: http://common-lisp.net/project/ucw/tarballs/parenscript-latest.tar.gz
Iterate is an iteration construct for Common Lisp. It can be found on
http://common-lisp.net/project/iterate/Download link: http://common-lisp.net/project/iterate/releases/iterate-current.tar.gz
Rfc2388 is a lisp implemantation of RFC 2388, which is used to process form data posted with HTTP POST method using enctype "multipart/form-data".
UCW uses its own fork of rfc2388. You can get the latest code from the darcs repository located at http://common-lisp.net/project/ucw/repos/rfc2388:
darcs get http://common-lisp.net/project/ucw/repos/rfc2388/
Daily snapshots are available at http://common-lisp.net/project/ucw/tarballs/rfc2388-latest.tar.gz
Rfc2109 is the lisp implementation of the cookie protocol. You can get it at: http://common-lisp.net/project/rfc2109/:
darcs get http://common-lisp.net/project/rfc2109/rfc2109
Daily snapshots are available at http://www.common-lisp.net/project/rfc2109/release/rfc2109-latest.tar.gz
splits sequences
info: http://www.cliki.net/SPLIT-SEQUENCE
download: http://ww.telent.net/cclan/split-sequence.tar.gz (you might have to select a mirror first)
If said link is dead, go to http://ww.telent.net/cclan-choose-mirror to delete your CCLAN-SITE cookie and choose another mirror. At least some of them are definitely working.
The Superior Lisp Interaction Mode for Emacs.
Download and install the latest CVS version of SLIME from http://common-lisp.net/project/slime.
To checkout from CVS you must first login to the repository:
export CVSROOT=:pserver:anonymous@common-lisp.net:/project/slime/cvsroot cvs login
Enter anonymous when prompted for the password. You can then check out the latest version with:
cvs checkout slime
It's swank you want to add to your asd systems dir. To solve a current problem with cl-launch you might want to overwrite your swank.asd file with this one:
http://cl-debian.alioth.debian.org/repository/pvaneynd/slime/debian/swank.asdCheck the description on cl-launch to see why.
Edi Weitz's regular expression library: http://www.weitz.de/cl-ppcre/.
Download the latest version: http://weitz.de/files/cl-ppcre.tar.gz
Networking library to create small server applications. Download the latest version from: http://ww.telent.net/cclan/trivial-sockets.tar.gz (you might have to select a mirror first.
If said link is dead, go to http://ww.telent.net/cclan-choose-mirror to delete your CCLAN-SITE cookie and choose another mirror. At least some of them are definitely working.
Or use asdf install.
All of the backends (except araneida) depend on Franz's open-source net.uri library. It only works on Allegro but Kevin Rosenberg has made a portable version called PURI.
For allegro users, NET.URI can be downloaded from here (it may be included in your version of acl): http://opensource.franz.com/uri/index.html
If you're not using allegro you'll need puri which can be downloaded from http://puri.b9.com/download.html
A portable pathname library based on code from Peter Seibels book Practical Common Lisp.
info: http://www.cliki.net/CL-FAD
download: http://weitz.de/files/cl-fad.tar.gz
A bash script to make your Lisp software easily invokable from the shell command-line.
info: http://www.cliki.net/cl-launch
download page: http://fare.tunes.org/files/cl-launch/
Download and add to your shell executable search path so ucw can find it while loading. Cl-launch can either be called cl-launch or cl-launch.sh, ucw will check for both. Also cl-launch depends on swank behavior from the debian swank.asd file supplied by apt-get and gets into trouble when loaded with other swanks, at least under sbcl. The easiest way to get around this problem is to overwrite your swank.asd file (in the slime directory root), with this one:
http://cl-debian.alioth.debian.org/repository/pvaneynd/slime/debian/swank.asdThe downside might be that swank won't work anymore as it should, although i haven't heard anyone about this yet. Another approach is to copy your swank*.fasls from the slime directory to the relevant shadow directory under ~/.cache/...
Check this thread for more info:
http://common-lisp.net/pipermail/slime-devel/2006-March/004664.htmlYes, ucw is bleeding edge ;)
A screen-like detachment tool to be able to detach and re-attach the lisp server from and to a console. Ucw uses a custom detachtty version, e.g. detachtty-9 plus a patch by Kevin Rosenberg. It accepts an eval argument when invoking a lisp, with obvious benefits. Get it through Darcs:
darcs get http://common-lisp.net/project/bese/repos/detachtty/
Go to the directory root and compile and install:
make make install
Ucw supports all the popular lisp backends. Whichever floats your boat. If you've got no clue which to choose I'd advice to go for mod_lisp. It works well with other languages, you get the benefit of apaches options and extras and all it does is pump the code straight to ucw so there's no trouble with quirks in the intermediate layer. Apache requires some work to function correctly, read the text below. The other backends should work out of the box, as long as they're recognized by asdf. aserve as well as portable aserve are in the package systems of gentoo and debian. Gentoo also supports mod_lisp and mod_lisp2 for apache and araneida. Read this blog post by Bill Clementson to form a more balanced opinion.
You can choose between apache 1 and 2. First of course you have to have a Apache web server up and runnnig; then you have to add the mod-lisp module by Marc Battyani which you find at http://www.fractalconcept.com/asp/html/mod_lisp.html
First download the mod_lisp c file. Watch out, don't follow the logic of the website. Except for the windows version the info is outdated concerning the apache 1 version and info about apache 2 is non-existent. Go to subversion from the download page or click the links below:
As stated in the intro, if you want to use ucw on a windows machine you're on your own. I have got no experience or information.
If you went for the c module on a unix clone, use the command
apxs -i -c mod_lisp.c
or
apxs -i -c mod_lisp2.c
for respectively apache 1 or 2. This will install the module in the appropriate apache directory. Then add the following lines to 'httpd.conf' in your apache configuration directory for both apache 1 and 2. Just be sure to change mod_lisp.so to mod_lisp2.so for apache 2.
# This goes near the other LoadModlue directives
LoadModule lisp_module modules/mod_lisp.so
LispServer 127.0.0.1 3001 ucw
<LocationMatch "/path/.*\.ucw"> SetHandler lisp-handler </LocationMatch>
This means that ucw INTERNALLY talks to apache on port 3001 at the internet address 127.0.0.1 (so in this case your localhost). The locationmatch part redirects all traffic from (in this case) 127.0.0.1 with prefix "/path/" and files ending on .ucw to ucw. So http://127.0.0.1/path/something.ucw as well as http://127.0.0.1/path/anotherpath/somethingelse.ucw will be redirected, but not http://127.0.0.1/somethingfaulty.ucw or http://127.0.0.1/path/somethingfaulty.html .
Should you want to use the aserve backend you will need to download and install either the AllegroServe if you use acl or portableaserve for any other. AllegroServe doesn't seem to have an asdf file, you're going to have to load it manually or however AllegroServe handles it's own files. Portableaserve does however.
AllegroServe can be downloaded from here (it may be included in your version of acl): http://opensource.franz.com/aserve/
portableaserve is hosted on sourceforge: http://portableaserve.sf.net
portableaserve download: http://constantly.at/lisp/aserve.tar.gz
To use portableaserve you'll also need the Allegro Common Lisp compatibility package (acl-compat). It's part of portableaserve and so it's home is also the before-mentioned sourceforge site.
acl-compat download: http://constantly.at/lisp/acl-compat.tar.gz
Should you want to use the araneida backend you will need to download araneida. Get the latest version just to be on the safe side. Originally araneida was meant to live behind apache and it can of course still be configured as such. Explaining this is beyond the scope of this document but have a look at this site for more info.
info: http://www.cliki.net/Araneida}
download: http://common-lisp.net/project/araneida/release/araneida-latest.tar.gz
or use darcs, although the connection seems a bit slow:
darcs get http://verisons.telent.net/araneida/
Ucw's in-house server. It's a simple server bundled with ucw. Nice for testing.
To download it or read up on it go to http://cliki.net/ASDF
Asdf makes sure common lisp packages and the files in those packages get loaded in the correct order. I would suggest you read the manual at http://constantly.at/lisp/asdf/. Really the first link is all you need to know right now.
Basically every lisp package you downloaded so far has defined an .asd file. Usually in the root of the directory. This file has to appear in the search path of asdf, which is a list of directories defined by the special variable asdf:*central-registry*. The directories are typically called "systems". Under a unix clone, make a symbolic link to a systems dir as described in the document or as shown below. If a package defines more than one .asd file, just shove the whole dir on the *central-registry*, again as described in the document or as shown below.
For example: on some linux installations the directory /usr/share/common-lisp/systems is in the list of asdf:*central-registry*. Now create a symlink from a particular .asd file to /usr/share/common-lisp/systems :
ln -s /path/to/library/lib.asd /usr/share/common-lisp/systems
To shove a whole directory on the *central-registry* do the following in your lisp startup file or in the repl:
(push #P"/path/to/asd/files/" asdf:*central-registry*)
All the required packages need to be recognized by asdf if you don't want to load every file by yourself.
Ucw also provides some packages which are kept out of the core so as not to bloat the program. Well two to be exact: the ucw presentations package and the ucw forms package.
An advanced CRUD interface for UCW.
darcs get http://common-lisp.net/project/ucw/repos/ucw-presentations
A more advanced HTML form toolkit for ucw than the one that is used in the core package, uses yaclml and dojo.
darcs get http://common-lisp.net/project/repos/ucw-forms
Note: this configuration will work for the current ucw_dev, not for ucw_public. For legacy support and ucw_public, follow the legacy asides in the various chapters.
At the moment the methods to configure and start ucw are a bit under development. Some nice additions made ucw more flexible but incompatible with former methods to simply start a server. Some entry level startup options will probably be added in the short run, but in the meantime we have the chance to immediately plunge in the deep end.
There are now two ways to start up ucw, both invoked through the command-line: via the ucw script ucwctl or directly via cl-launch.
The easy route to walk before setting things up is using cl-launch, to which a number of ucwtl's configure options are passed. It provides a suite of launch options to invoke lisp from the command-line. For general cl-launch understanding, read the cl-launch documentation. I assume you followed the installation instructions for cl-launch above. If you have root access to your box, copy the files at /path/to/ucw_dev/bin/etc/ to /etc/ucw/ to use the default settings. Now you can test if ucw works by typing at a *nix shell prompt:
export CONFIGFILE=/etc/ucw/conf.lisp cl-launch -X -l your-lisp-invoke-name -p /path/to/ucw/dependency/asd/files/dir NO_QUIT=yes -- /etc/ucw/start.lisp
Without having configured anything this will start ucw with the built-in httpd server. Point your browser at http://127.0.0.1:8080/ and cross your fingers.
The code fragment looks verbose enough but i can not resist to explain: the shell environment variable CONFIGFILE is used by ucw in the startup process. -X stands for execute, -l for which lisp to invoke, -p for the dir where your ucw-dependent .asd files are stored. You can supply multiple -p's for more dirs. Be ware that you can't yet supply more than one dir through ucwctl. NO_QUIT isn't defined in the cl-launch script; people familiar to shell scripting will or will not know what this is for. The last bit of text is the startup file for ucw, which you can pretty much leave as nowadays. More on conf.lisp later on.
Over to ucwctl. First to comply with the standard setup, create /etc/ucw/applications.d:
mkdir /etc/ucw/applications.d
In this dir you will keep the .asd files of the applications you create for easy loading. More on that in the next chapter.
It's convenient to put ucwctl in the *nix executable search path but contrary to cl-launch it is not mandatory. Here's the ucwctl help output; -a, -c, -l and -s are passed to cl-launch:
Usage: ucwctl [options] command
Options:
-a, --asdf-root DIRECTORY Register asdf search DIRECTORY -c, --config-file FILE Read FILE as config file. -h, --help Display this help and exit. -l, --lisp NAME Use NAME (without PATH) as Common Lisp implementation. --log-root DIRECTORY Save the logs in DIRECTORY, overriding the --var-root option. --run-root DIRECTORY Created pid/socket in DIRECTORY, overriding the --var-root option. -s, --start-file FILE Use FILE as start file. --timeout SECONDS Set timeout in SECONDS when trying to stop a a running session. --var-root DIRECTORY Save the logs in DIRECTORY/log/ucw and create pid/socket in DIRECTORY/run/ucw, if the two options --log-root and --run-root are not set.
Commands:
start Start the UCW server. stop Shutdown the UCW server and exit from lisp. attach Attach to the socket specified in the config file.
All the options, except for --log-root and --run-root are configurable through /etc/ucw/ucwctl.conf . In ucwctl.conf default values are provided except for the lisp implementation and the .asd directory. If you have configured those last two, you have put the files from /path/to/ucw_dev/bin/etc in /etc/ucw/ and you are root, you can start ucw by invoking:
ucwctl start
Without having configured anything (in /path/to/conf.lisp) this will start ucw with the built-in httpd server, just as with cl-launch. But now you have log files and more importantly you are not tied to a particular console. Point your browser at http://127.0.0.1:8080/ and hope for the best.
If you don't like to run as root for your day do day development, you can change the standard logging and socket path to a dir you have write-access to; either in ucwctl.conf or through the ucwctl options.
To stop ucw type:
ucwctl stop
To attach to a running ucw server type:
ucwctl attach
This calls attachtty, installed alongside detachtty, and passes attachy the socket defined in the ucwctl startup script. De-attach attachtty by giving it a sighup signal, eg by closing your terminal window or by pressing ctl-\. Note that you should be a bit careful when you attach to a socket and you start typing away at the repl. By default cl-launch will terminate the running process when the debugger is invoked. This behavior is changeable; check the cl-launch script.
To test if ucw works with your chosen backend open /path/to/conf.lisp (to be found in /etc/ucw/conf.lisp if you copied it from /path/to/ucw_dev/bin/etc/) and change:
(setf *ucw-backend-type* ':httpd) (setf *ucw-backend-host* "127.0.0.1") (setf *ucw-backend-port* 8080)
to:
(setf *ucw-backend-type* ':mod_lisp) (setf *ucw-backend-host* "127.0.0.1") (setf *ucw-backend-port* 3001)
or to:
(setf *ucw-backend-type* ':araneida) (setf *ucw-backend-host* "127.0.0.1") (setf *ucw-backend-port* 8080)
or to:
(setf *ucw-backend-type* ':aserve) (setf *ucw-backend-host* "127.0.0.1") (setf *ucw-backend-port* 8080)
Repeat your chosen startup procedure for ucwctl with th note that if you use mod_lisp you have to point your browser at http://127.0.0.1/path/you/specified/in/apache/this/link/goes/to/the/root/index.ucw, without the 8080 (defaults to the server root here).
note also that the default conf.lisp configuration defaults to the configuration in /path/to/ucw_dev/src/default.lisp which is the default configuration ;-) So if you don't change anything in conf.lisp you might just as well omit it.
The variables in conf.lisp that we didn't cover yet are:
(setf *ucw-swank-port* 4005)
- used to connect to emacs, et al.
(setf *ucw-server-class* 'standard-server)
- leave as is. I'm not even sure if there is a non-standard server we should be interested in.
(setf *ucw-inspector* t)
- let's you inspect ucw inside the html pages you made when set to t. Seeing is believing. Set to nil to see the page as normal.
(setf *ucw-debugger* t)
- sends debugging html to the client when set to t. Seeing is believing. Set to nil to not send debugging info to the client and to invoke the debugger in your editor.
(setf *ucw-log-level* '+INFO+)
- Sets the log-level threshold for the information that will be logged by the base ucw logger and which will be pumped to the ucw.log file. these are the log levels to choose from, together with their value:
+dribble+ = 0
+debug+ = 1
+info+ = 2
+warn+ = 3
+error+ = 4
+fatal+ = 5
So in our example debug info and dribble will not be logged to ucw.log.
Note that this procedure is not strict necessary, you could just fiddle around in ucw-user, but you will want to have your own proper environment sooner or later. To get the best of both worlds (being lazy and having a proper set up environment), just download the code we're going to create below (mod_lisp users, check the www-dir stuff below for a correct setup). For the less lazy, create a directory where you want to keep your application code. (the following is halfway ripped from the cliki tutorial. thanks ;-)).
Create an asdf file called ucw-intro.asd will contain:
(defpackage :ucw.intro.system (:use :cl :asdf))
(in-package :ucw.intro.system)
(defsystem ucw-intro :version "0.0.0.0.1" :depends-on (:ucw) :author "a lonely clown" :components ((:file "packages") (:file "ucw-intro" :depends-on ("packages"))))
and a package file called packages.lisp:
(in-package :cl-user)
(defpackage :ucw-intro (:use :common-lisp :it.bese.ucw :it.bese.arnesi :it.bese.yaclml))
make a symlink from your asd file to the applications.d dir you defined in the last chapter.
Important: when ucw loads it checks this directory for .asd files of your ucw applications. To let them load correctly you have to synchronize four names in your applications with standard pre- and suffixes:
There is also an alternative way of loading your applications when loading ucw. In /etc/ucw/conf.lisp (or where-ever you decided to put the file), push the name of your applications asd system name on *ucw-systems* and push it's application name (see below in the ucw-intro.lisp code), on *ucw-applications* like it's done for the ucws example applications:
(defvar *ucw-systems* '(:ucw.admin :ucw.examples)) (defvar *ucw-applications* '(it.bese.ucw::*admin-application* it.bese.ucw-user::*example-application*))
If you don't want them to load, take them away from both conf.lisp and /path/to/ucw_dev/src/default.lisp.
Just loading the three files we define(d) in this chapter while ucw is running will also register them correctly, no matter how we name the before-mentioned names.
We also need a www root directory. This is the directory where we store static files for the http server to see: css and javascript files, pictures and the like. Lisp servers handle the www directory a bit different than mod_lisp so we have to make apt provisions. Lisp servers can themselves map a path from an incoming uri to the underlying directories on a server. When we use mod_lisp we have to tell apache as well as lisp where to go.
When you use a lisp server backend, just create a directory in the tutorial root and call it www if you want to be in sync with this tutorial. With mod_lisp create a directory somewhere in the apache www tree and in range of the mod_lisp search path as explained in the installation section. With apache 2 a typical path would be /var/www/locahost/htdocs/ucw-intro. The apache 1 www root normally also lies somewhere within that vicinity. Then link that one to your local dir for easy reference:
ln -s /var/www/localhost/htdocs/ucw-intro /path/to/ucw-intro/www
Now we create a new file in our example application root called ucw-intro.lisp and type or copy:
(in-package :ucw-intro)
(defvar *www-root* (merge-pathnames (make-pathname :directory '(:relative "www")) (asdf:component-pathname (asdf:find-system :ucw-intro))))
(defparameter *ucw-intro-application* (make-instance 'cookie-session-application :url-prefix "/ucw-intro/" :www-roots (list *www-root*) :debug-on-error t))
(defcomponent hello-world (simple-window-component) () (:default-initargs :title "hi" :stylesheet '("stylesheet.css" "stylesheet2.css") :content-type "text/html; charset=utf-8;" :javascript '((:src "dojo.js") (:js (dojo.require "dojo.event.*")) (:script "var foo = 3;"))) (:entry-point "^(|index.ucw)$" (:application *ucw-intro-application* :class regexp-entry-point)) (:render () (<:as-html "hello world")))
Restart ucw and surf to http://127.0.0.1(:8080)/ucw-intro/index.ucw, cross your fingers... and... now you've printed "hello world"! (If the heavens are willing).
In the next chapter we will see what applications actually are, and we will examine the contents of ucw-intro.lisp. Also we will discuss the mechanics of ucw. Actually the next chapter is the most important chapter of this intro. Finally the foreplay is over.
In the course of this chapter we will take a flight through website-making history. We begin with traditional website-making techniques and we will quickly work our way up. In the process you can't help but be introduced to the basic building blocks of a ucw program: components, actions, entry-points, applications and render methods. Ready made source is available.
Component have more than one role. First they act as your basic storage unit. They are classes and you can define them in the same way, but they have added functionality given through meta object protocol modifications. To explain these modifications we need some background on the inner workings of ucw: When ucw receives a http request it checks if the request is a part of user session already in progress. If not it creates a new one. Such a session is made up of frames. Every new request makes a new frame which is referenced by a random string. Every frame copies the values of relevant components and their slots from the frame before, except of course when the values of a given class have changed. If a user wants to go back to a previous page, the appropriate frame is looked up with the help of the identifier string (be it through get or a cookie), and the original values are restored. So frames are the memory of a ucw session. Some of the modifications made to components facilitate this process of getting and setting values in frames under the cover.
Components are also clothes-hangers for html output. You generate html by defining render methods on them. In the case of our example the method renders (as you say) the simple-window-component hello-world, which is a pre-defined component to create an empty canvas. You can nest render methods of other components, in which case you piece the data together to form a complete page. With this information we can step through ucw-intro.lisp from the last chapter:
(in-package :ucw.intro)
(defvar *www-root* (merge-pathnames (make-pathname :directory '(:relative "www")) (asdf:component-pathname (asdf:find-system :ucw-intro))))
(defparameter *ucw-intro-application* (make-instance 'cookie-session-application :url-prefix "/ucw-intro/" :www-roots (list *www-root*) :debug-on-error t))
(defcomponent hello-world (simple-window-component) () (:default-initargs :title "hi" :stylesheet '("stylesheet.css" "stylesheet2.css") :content-type "text/html; charset=utf-8;" :javascript '((:src "dojo.js") (:js (dojo.require "dojo.event.*")) (:script "var foo = 3;"))) (:entry-point "^(|index.ucw)$" (:application *ucw-intro-application* :class regexp-entry-point)) (:render () (<:as-html "hello world")))
First we define the filesystem directory which is going to function as www-root for static data such as images, style-sheets and the like. We feed it to the definition of our first application object. An application is an arbitrary conceptual construct with which you define a number of options for a block of web-pages/site-logic. It lets you specify if you want to use cookies, what charset you want to use, etc. A quick tour of the chosen options: In this instance we want to use cookies, which means that ucw starts searching for certain cookie information after it has looked for information in other places. The url prefix is the browser path relative to the server root. Www-roots is a list of pathnames the application searches to get to static data relative to the the APPLICATION root. They are set relative to the url-prefix, eg in this example from the outside the www-root of ucw-intro is 127.0.0.1/ucw-intro/.
The simple-window-component class and the defmethod macro that defines it can together be seen as as a convenience layer which constructs the underlying building blocks a standard application wants to have. In this section you will learn the semantics of both and you will learn how to define the building blocks without them.
A simple-window-component sets up your basic html layout. You can feed a title, stylesheets, content-types, and different ways to insert javascript to :default-initargs. All of them accept a list as well as a basic unit suitable for the keyword, although you probably don't want to pass a list to the title argument as that will just print a quoted list. You can specify one or more javascript source files with the :src key, using :script lets you insert javascript as is and :js lets you insert javascript in parenscript syntax. With this information it sets up a whole page, except for the body. That's left for the render method.
Defcomponent defines components almost the same as defclass defines classes. :default-initargs, slots, etc... are simply passed to the underlying defclass. But it has some extra options. You can supply a default backtrack function with :default-backtrack, you can supply a render method with :render, you can supply an entry-point with :entry-point and you can supply an action with :action. More on those last two in the next section, and more on :default-backtrack in a future chapter.
As stated before, a render method is a regular method that specializes on a given component to output html. It is called when the program decides when it's time to put together a page to show the user. More on program flow in the next section of this chapter. The defcomponent macro passes the arguments from the :render form to a defmethod and let's it specialize on the name given to defcomponent. If no name is given the name defaults to the name given to defclass. There are two standard ways to output html in a render method. First you can use the <:as-html macro and just type big slabs of html-escaped text or use <:as-is to produce un-html-escaped text, but you know as well as I that lispers would rather be tortured for weeks by the US government than not lispify foreign syntax. Ucw uses yaclm for this, which is the in-house html markup lispifier. Html tag names start with <:, attributes are keywords followed by their value. The standard html format that ucw spits out is xhtml 1 (eg html 4 but more strict). The whole of xhtml 1 is defined in yaclml and work has just started on svg tags. That's all there is to it.
Without defcomponent and simple-window-component you could have written the code like this:
(defclass hello-world2 () () (:metaclass standard-component-class))
(defentry-point "^(|index2.ucw)$" (:application *ucw-intro-application* :class 'regexp-entry-point) () (call 'hello-world2))
(defmethod render ((hello-world2 hello-world2)) (<:as-is "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/transitional.dtd\">" #\Newline) (<:html (<:head (<:meta :http-equiv "Content-Type" :content "text/html; charset=utf-8;") (<:title (<:as-html "hi")) (<:link :rel "stylesheet" :href "stylesheet.css" :type "text/css") (<:link :rel "stylesheet" :href "stylesheet2.css" :type "text/css") (<:script ;; most browsers (firefox, safari and ie at least) really, ;; really, really don't like empty script tags. The "" forces ;; yaclml to generate a seperate closing tag. :type "text/javascript" :src "dojo.js" "") (<:script :type "text/javascript" (<:as-is (js:js* '(dojo.require "dojo.event.*")))) (<:script :type "text/javascript" (<:as-is "var foo = 3;"))) (<:body (<:as-html "hello world 2"))))
What we have omitted in this example is giving the components extra slots. Declaring slots in components is like defining slots for regular classes with two extra options which are :component and :backtrack. The former is for nesting components, the latter for backtracking values in other frames. Both will be discussed in future chapters.
You might have heard some fellow that touted ucw as being hard. Well, this is the hard part. That is, to figure out how it ticks. But we don't need to know that right now. We just need a working model of basic control mechanics: When a user calls a ucw page for the first time, without being redirected by another ucw page and without information by some ucw cookie that hasn't timed out yet, the machinery checks the last form you just typed at the defentry-point form, in this case (call 'hello-world2). It calls the component hello-world2, which means it locks that component as being the main component to be rendered to the user. Whenever ucw gets user input it makes a new frame and after the preliminaries are over, it calls the render method on the component that's at that moment the main component and the page is served. The end part of entry points and so called actions handle the control flow of a ucw program between page calls. The end part of entry-points doesn't have to be just one form. After the second form, to which we will turn later, you can type any number of forms, which will be executed in order.
On the face of it that call to hello-world doesn't seem to do so much out of the ordinary, and that's the whole point. As you might know the web poses some problems for standard program execution because web-pages slash your flow in pieces. Those pieces have to be pieced together again somehow. It turns out that cps (continuation programming style) is quite handy for this purpose. Basically it means being able to capture the state of your program anywhere you want in a function. You can call that function again whenever you want and how often you want to pick up the execution of your program where you left off. Some languages have facilities built in to just capture a program state like that but lisp doesnt. You have to transform the code to be able to do this. You could do this by hand if you're mad, but ucw has a cps transformer on board that does it for you. This information might be a bit vague. If you want to read up on the subject i suggest you read chapter 20 of On Lisp.
Now all of this transforming is done under the cover so you won't have so much to do with cps transformation but there is actually also a practical purpose for knowing this, and to explain that we need yet some more theory. A cps transformer must sort of understand the semantics of the code it's working with so it needs a code walker to interpret for example if forms are macros or how to keep track of environment variables. Every implementation obviously has one but language users don't have access to it. You have to roll your own and ucw did. But walking code ain't as easy as it not sounds. Code walkers usually don't capture lisp perfectly and the ucw one is no exception. Usually stuff works out fine but don't be surprised if your code doesn't do what it should at the end of a defentry-point or inside an action. When you have to handle a lot of code inside an action, try to delegate as much as possible to function calls, because functions outside an action are not subject to these laws of an alternate lisp universe.
Just some more theory so you fully appreciate the example beneath. At some point an entry point should call a component with (call ... , which initializes the component with initargs passed to it and the component is set as THE component for that frame. It is rendered, perhaps together with sub-components it specifies to render in it's render method. As the user is presented with a page, she/he gets her/his first chance to interact with the program through the usual mechanics: forms, links, buttons. The user clicks or writes something and another request is inbound. The traditional (some whisper 'pre-ucw') way to handle a request is to check get and post values and act on them. Ucw sports this facility. In our example the form before the call to hello-world2 is empty. In that form you could have specified which parameters from the page you want to use. If you want to give the parameter a default value you can put parameter and default between parenthesis. These values are now available for use in the forms you write after them.
But of course ucw has more tricks up it's sleeve. For some html tags ucw has it's own yaclml equivalent with extended functionality: a, area, button, form, select, option, textarea and input. For convenience ucw gives the input attributes text, password and submit their own tag. Furthermore ucw sports such outlandish tag-names as integer-range-select, month-day-select, render-component and script. These last ones we will cover in a later chapter. All these tags become invocable macro-forms by putting <ucw: in front of them. See the example.
The extra functionality comes from the extra keywords you can pass them: :action, :accessor, :reader and writer. The macro's can handle one or more of these, depending on the macro, but the keywords are mutually exclusive. With the :action keyword you can specify an action form which will be executed with the relevant parameters when the form returns. With :accessor you can specify a place. It's value is the initial value of the specified tag. If the tag returns a value, the place will store it. :reader and :writer are used as a team to provide more flexibility than :accessor does. If provided, the value of :reader is used as the initial value of place. The value of writer should be a function which accepts one argument: the value returned by the tag.
Continuing our control flow we assume the user uses one of the before-mentioned ucw forms to once again bother the server with info. If the user hasn't sparked off one of the ucw forms with one of those so-called actions attached to them, the current component stays in place. When a new page is rendered, the render method will again specify on this component. When an action is called it can do a lot of things but seen through this prism, the options split up in three: an action can execute code without ever calling a call to another component, it can somewhere along the line call another component or it can answer a component. When it never calls
(call ...
or answer
(answer ....
the component stays in place. When it calls another component, that component gets focus and ucw stores a continuation of where that action got interrupted in that component. When an action answers, ucw restores the component that was in focus before it called the component that just answered (heh). The continuation that was stored in that component is called with an optional value which is the value that's returned by the call form. The action in which the call was made continues untill it either calls a component, answers a component or ends, in which case the component connected to the action gets rendered. The entry point is a bit special in this story in that you can't answer in an entry point, because there is no component to answer to. In stead entry points act like infinite loops.
It's high noon for an example. From defcomponent downwards rewrite hello-world like so:
(defcomponent hello-world (simple-window-component) ((cow :initarg :cow :accessor cow :initform "aspacia") (pig :initarg :pig :accessor pig :initform "knorrie")) (:default-initargs :title "hi") (:entry-point "^(|index.ucw)$" (:application *ucw-intro-application* :class regexp-entry-point)) (:render () (with-slots (cow pig) hello-world (<:as-is "in the old days, to update the name of your lovely cow named <b>" #\Newline) (<:as-html cow) (<:as-is "</b> you would update it like so: ") (<:form :action "get.ucw" :method "GET" (<:text :name "cow") (<:submit :value "name your cow")) (<:p (<:a :href "get.ucw" "go to get without any value to get to the default value of the entry point")) (<:p (<:as-html "but we have left them behind. In the brave new world we will update the name of our lovely pig like: ") (<ucw:form :method "POST" (<ucw:text :accessor pig) (<:submit :value "name your pig"))) (<:p (<:as-html "to pass a value and control to another component we do like so:") (let ((stuff-to-say "")) (<ucw:form :action (get-stuff-to-say hello-world stuff-to-say) :method "POST" (<ucw:textarea :accessor stuff-to-say) (<:br) (<:submit :value "have something to say")))) (<:p (<ucw:a :action (jump 'office) "jump to office"))))) (defentry-point "^(|get.ucw)$" (:application *ucw-intro-application* :class 'regexp-entry-point) ((cow "moo")) (+ 2 2) (let ((cow (concatenate 'string "mighty " cow))) (call 'hello-world :cow cow)))
(defaction get-stuff-to-say ((hello-world hello-world) stuff-to-say) (let ((stuff-to-say (concatenate 'string stuff-to-say " (boring!!!)"))) (setf (cow hello-world) (call 'the-other-one :stuff-to-say stuff-to-say))))
(defaction ok ((c component) &optional (value c)) (answer value))
(defcomponent the-other-one (simple-window-component) ((stuff-to-say :initarg :stuff-to-say :accessor stuff-to-say)) (:default-initargs :title "hi" :content-type "text/html; charset=utf-8;") (:render () (<:p (<:as-html "- "(stuff-to-say the-other-one))) (<:p (<:as-html "yeah, very 'enlightning'. Dazzle me some more: ")) (<ucw:form :method "POST" (<ucw:textarea :reader (stuff-to-say the-other-one) :writer #'(lambda (x)(setf (stuff-to-say the-other-one) (concatenate 'string x " (boring !!!!!)")))) (<:br) (<:submit :value "have something to say")) (<:br) (<ucw:a :action (ok the-other-one "nice to be back") "back to the last component")))
First we observe the added slot to hello-world called cow. We use it to store the values passed by our forms. The first form shows the old way: The name cow is picked up by the defentry-point below it, which we also let calculate 2 + 2 for no reason at all and in which we add mighty or puny to cow. The link below it shows that if no parameter named cow turns up the value of cow is set to moo. Notice however that if an empty form is submitted the name of cow is set to zero and our cow is nameless but still mighty or puny. Our second form has to be an ucw form if we want the <ucw:text tag to work. The text tag only needs :accessor to work correctly; no need for pages to redirect to and no need for a name. The submit button could have been omitted, just as with the normal form. The :accessor method circumvents the entry-point for the value of pig. the form method could have been either get or post. For the correct working of ucw it doesn't matter which you choose. If you click the back to entry point link, you pass control back to the entry-point action. which proceeds to it's next call. The ok defaction is already provided by ucw.
The third form shows how to pass control to another component. We (I at least) want to fill the slot stuff-to-say of component the-other-one with a value passed by the form. Since they of course don't share the same slot we make a temporary variable to have something to hang on to. When we submit the form we execute the action get-stuff-to-say which passes control to stuff-to-say with the slightly modified stuff-to-say. So now we can contain the generalized behavior of an entry-point within a function which can be tailor-made to fit one tag. Notice it doesn't matter if you call the next component in either index.ucw or get.ucw. The only interesting thing in the form on the the-other-one page is that it uses reader and writer. The link below it gives control back to hello-world. As you can see you can give ok and answer an extra value which will be the value returned by the call form. Notice the value of pig is how you left it. If you hadn't changed the name of your cow it would also still be the same.
There you have it. You mastered the basic ucw mechanics, but of course you would like some more tools in your kit to let ucw work on code in stead of you. As you saw in the index: templating, extended form handling, login code and more is provided. This kind of functionality is the subject of the next few chapters.
There are two ways of templating in this chapter that scramble for the same namespace. The first is concerned with component manipulation; this includes nested components and containers. The second, the ucw TAL (Template Attribute Language) implementation, is a tool used by ucw to insert (almost) plain html into ucw code so that non-lispers (for example designers) can easily contribute to a ucw project. Get the code.
Nested components are components that occupy the slot of another component. They are not that different from regular slot values except for that you want them to inherit the proper values from past frames. To set these values, nested components use :component as their initialization keyword when placed in a component's slot. In day to day use they behave just as other slot values. If you want to use them to nest html layout, define a render method on them and invoke it in their parents render method.
Containers are used to store components. Their main use is to be able to select one of those components as the component to be rendered to the user. The components in the container are stored as a list and can be referenced by label or index. This is done through several methods associated with containers to manipulate their contents.
Containers come in three flavors: container, simple-container and list-container. A bog-standard container is a component with three extra slots:
Containers are very nice for letting people navigate a site. You nest one inside a window-component with :component and present the user with a list of navigation links to which you attach a switch-component action (already provided by ucw) which accepts the container in question and a label name as an argument. The action sets the label of the component which is associated by label as the current-component-name.
A simple-container is just like a normal container except it already has a render method associated with it which simply renders the component associated with the current-component-name.
A list-container is a child of container-component as well as widget-component. It's render method renders the components in the container as an ordered list. A list container has a custom initialize-instance method defined on it, which accepts an additional orientation keyword which can be set to either :horizontal or :vertical. If one of these keywords is supplied, it is added to that component's css class list (see the widget-component documentation in chapter to-be-written).
The container-manipulating methods are in order of appearance:
The following example demonstrates the use of a simple-container, nested in a window-component. It's a simple mock tragic adventure fragment. When all drawers are checked, a new component is shoved onto the containers contents list. The flet at the end and the simple-container initialization in the room class are the most interesting. The simple-container mechanics code is mostly taken from the example application, bundled with ucw. Start it up to see a more traditional use of containers.
(defcomponent timeline (widget-component) ((history :initarg :history :accessor history)) (:render () (<:as-html (history timeline))))
(defcomponent drawer (widget-component) ((contents :initarg :contents :accessor contents)) (:render () (<:as-html (contents drawer))))
(defcomponent office (simple-window-component) ((what-happened :initform '(0 0 0) :accessor what-happened) (closet :component (simple-container :current-component-name 'in-office :contents `((in-office . ,(make-instance 'timeline :history "You stand alone in the office of your evil boss. In front of you you see a closet in which you are sure you will find valuables with which you can pay off your insurmountable debts.")) (top-drawer . ,(make-instance 'drawer :contents "you find memoirs of a broken soul")) (middle-drawer . ,(make-instance 'drawer :contents "you find an agenda of grinding daily chores")) (bottom-drawer . ,(make-instance 'drawer :contents "you find an ointment to take the edge off")))) :accessor closet)) (:render () (with-slots (what-happened closet) office (unless (find-component closet 'leave) (case (container.current-component-name closet) ('top-drawer (setf (car what-happened) 1)) ('bottom-drawer (setf (cadr what-happened) 1)) ('middle-drawer (setf (caddr what-happened) 1))) (when (and (= (car what-happened) 1) (= (cadr what-happened) 1) (= (caddr what-happened) 1)) (add-component closet (make-instance 'timeline :history "You leave the office ashamed and angry. You know now your evil boss is also a human being and you will have to feel compassion for him forever.") 'leave))) (render closet) (<:br) (<:as-html "What will you do next?") (<:br) (unless (eql (container.current-component-name closet) 'leave) (flet ((drawer-link (name text) (<ucw:a :action (switch-component (closet office) name) (<:as-html text)))) (<:ul (<:li (drawer-link 'top-drawer "inspect top drawer")) (<:li (drawer-link 'middle-drawer "inspect middle drawer")) (<:li (drawer-link 'bottom-drawer "inspect bottom drawer")) (<:li (drawer-link 'in-office "recall what your doing here")) (when (find-component closet 'leave) (<:li (drawer-link 'leave "leave")))))) (<:br) (<ucw:a :action (jump 'hello-world) "go back to where you came from"))))
to be written
is here.