Roadmap to UCW Codebase

Abstract
Roadmap
Applications
Cookie
L10n
Secure
Components
Windows
Containers
Dialogs
Forms
Templates
Utility Mixin Components
Control Flow
Calling
Actions
Entry Points
Dispatching
Simple Dispatcher
Server
Debugging
Inspector
Tips
Getting dojo to load
Specials Bound During Rendering
Printing to the yaclml stream

Abstract

UnCommon Web is a very powerful and mature web framework for Common Lisp, but is a bit difficult to learn. It is documented extensively—in the form of docstrings. These are extremely helpful once you've figured out the rough structure of UCW, but they are of no help when first learning unless you just read most of the source. I ended up having to do that, and after some urging along by folks in #ucw I decided to clean up my planner notes and publish them for public consumption.

The roadmap is presented with major sections ordered in a logical order for learning the framework. The sections are ordered internally in order of most immediately useful to least, but it may be worth hopping between major sections before reading all of the details. I have used abridged class definitions and docstrings with occasional commentary to clarify things.

Roadmap

Applications

Applications are a bundle of entry points. The base class is, naturally, standard-application, but you should instead derive your application class from modular-application and any standard or custom application mixins you find useful.

src/rerl/standard-classes.lisp

(defclass standard-application (application)
  ((url-prefix :initarg :url-prefix
               :documentation "A string specifying the
start (prefix) of all the urls this app should handle.

This value is used by the standard-server to decide what app a
particular request is aimed at and for generating links to
actions within the app. ")
   (www-roots :initarg :www-roots
              :documentation "A list of directories (pathname
specifiers) or cons-cell (URL-subdir . pathname) to use when looking for static files.")
   (dispatchers :initarg :dispatchers
                :documentation "A list of request
dispatchers. The user supplied list of dispatchers is extended
with other dispatchers that are required for UCW to function
properly (action-dispatcher, a parenscript-dispatcher, etc). If
you want full control over the active dispatchers use the (setf
application.dispatchers) accessor or, if you want control over
the order of the dispathcers, (slot-value instance
'dispatchers)."))
  (:documentation "The default UCW application class."))

src/rerl/modular-application/modular-application.lisp

(defclass modular-application-mixin ()
  ()
  (:documentation "Superclass for all application mixins."))

(defclass modular-application (standard-application modular-application-mixin)
  ...)

Cookie

src/rerl/modular-application/cookie-module.lisp

(defclass cookie-session-application-module (modular-application-mixin)
  (:documentation "Class for applications which use cookies for sesion tracking.

Cookie session applications work exactly like
standard-applications except that when the session is not found
using the standard mechanisms the id is looked for in a cookie."))

This is the most useful of the application components. It makes your application urls readable by stashing the session id into a cookie rather than as a set of long and ugly GET parameters.

L10n

src/rerl/modular-application/l10n-module.lisp

(defclass l10n-application-module (modular-application-mixin)
  (:documentation "Application class which can handle l10n requests."))

Secure

src/rerl/modular-application/security-module.lisp

(defclass secure-application-module (modular-application-mixin)
  (:documentation
    "Mixin class for applications which require authorized access.
Concrete application must specialize the following methods:
APPLICATION-FIND-USER (APPLICATION USERNAME)
APPLICATION-CHECK-PASSWORD (APPLICATION USER PASSWORD)
APPLICATION-AUTHORIZE-CALLZE-CALL (APPLICATION USER FROM-COMPONENT TO-COMPONENT)."))

Components

A component is a special class that handles the complexities of continuation suspension and such for you. New components are derived from the existing ones by using defcomponent instead of defclass. This adds a few extra slot and class options, and ensures that the proper metaclass is set.

src/rerl/standard-component/standard-component.lisp

(defmacro defcomponent (name supers slots &rest options)
  "Macro for defining a component class.

This macro is used to create component classes and provides
options for easily creating the methods which often accompany a
component definition.

NAME, SUPERS and SLOTS as treated as per defclass. The following
extra options are allowed:

 (:ENTRY-POINT url (&key application class)) - Define an
 entry-point on the url URL which simply calls an instance of
 this component. Any request parameters passed to the entry-point
 are used to initialize the slots in the component. This option
 may appear multiple times.

 (:DEFAULT-BACKTRACK function-designator) - Unless the slots
 already have a :backtrack option FUNCTION-DESIGNATOR is
 added. As with the 'regular' :backtrack options if you pass T
 here it is assumed to mean #'IDENTITY.

 (:RENDER (&optional COMPONENT) &body BODY) - Generate a render
 method specialized to COMPONENT. COMPONENT may be a symbol, in
 which case the method will be specialized on the componnet
 class. If COMPONNET is omited the component is bound to a
 variable with the same name as the class.

 (:ACTION &optional NAME) - Generate a defaction form named
 NAME (which defaults to the name of the component) which simply
 CALL's this component class passing all the arguments passed to
 the action as initargs.")

;;; Extra Slot Options
"Other than the initargs for standard slots the following
options can be passed to component slots:

:backtrack [ T | NIL | FUNCTION-NAME ] - Specify that this slot
should be backtracked (or not if NIL is passed as the value). If
the value is neither T nor NIL then it must be a function which
will be used as the copyer.

:component [ TYPE | ( TYPE &rest INITARGS ) ] - Specify that this
slot is actually a nested component of type TYPE. When instances
of the class are created this slot will be set to an instance of
type TYPE and it's place will be set to this slot. If a list is
passed to :component then TYPE (which isn't evaluated) will be
passed as the first argument to make-instance. The INITARGS will
be eval'd and apply'd to make-instance. The result of this call
to make-instance will be used as the effective component
object."

Windows

A window-component represents a top level browser window, naturally.

src/components/window.lisp

(defclass window-component ()
  ((content-type)))

(defclass simple-window-component (window-component)
  ((title)
   (stylesheet)
   (javascript :documentation "List of javascript includes.

Each element must be a list whose first value is either the
symbol :SRC or :JS.

 (:SRC url) - writes <script src=\"URL\"></script> tag.
 (:JS form) - equivalent to (:SCRIPT (js:js* form))
 (:SCRIPT string) - write <script>STRING</script>.

The elements will be rendered in order.")
   ...))

window-component could be useful for doing things like dumping binary data to the user, or just deriving your own funky top level window type.

simple-window-component is the easiest for displaying standard webpage. It provides a wrapping method on render that displays the html boilerplate based on your component slot values which is what one wants most of the time. The initargs to simple-window-component have the same names as the slots.

Status Bar

src/components/status-bar.lisp

There is a generic status bar interface. Messages severity is one of (:error :warn :info). Note that the default status bar render method just shows a div with status messages. A derivative could be defined to insert messages into the browser status bar.

(defcomponent status-bar ()
  ((messages :documentation "An ALIST of the messages to
show. Each element is a cons of the form (SEVERITY .
MESSAGE). SEVERITY is one of :ERROR, :WARN, :INFO and MESSAGE is
a string which will be html-escaped.")
   ...)
  (:documentation "Stateless status bar to display messages."))

(defgeneric add-message (status-bar msg &key severity &allow-other-keys)
  (:documentation "Add the message text MSG to STATUS-BAR with
severity SEVERITY."))
(defcomponent status-bar-mixin ()
  ((status-bar :accessor status-bar
               :initarg status-bar
               :component (status-bar))))

(defmethod show-status-bar ((win status-bar-mixin))
  (render (status-bar win)))

(defgeneric show-message (msg &key severity &allow-other-keys)
  (:documentation "Show a message in the status bar. Only works if
  current window is a status-bar-mixin"))
Redirect

src/components/redirect.lisp

(defclass redirect-component ()
  ((target :accessor target :initarg :target))
  (:metaclass standard-component-class)
  (:documentation "Send a client redirect.

This component, which must be used as a window-component,
redirects the client to the url specified in the target slot. A
302 (as opposed to 303) response code is sent to ensure
compatability with older browsers.

The redirect component never answers."))

There is also a meta-refresh procedure.

(defun/cc meta-refresh ()
  "Cause a meta-refresh (a freshly got (GET) url) at this point.
This is useful in order to have a GET url after a form POST's
actions have completed running. The user can then refresh to his
heart's content.")

Containers

src/components/container.lisp

(defclass container ()
  (...)
  (:metaclass standard-component-class)
  (:documentation "Allow multiple components to share the same place.

The container component serves to manage a set of components.
It does not provide any render impementation, which is the
resposibility of the subclasses (e.g. switching-container or
list-container).

Each contained component has a \"key\" associated with it which
is used to retrieve a particular component. Keys are compared with
container.key-test.

The :contents inintarg, if provided, must be either a list of (key .
component) or a list of components. In the latter case it will
be converted into (component . component) form."))
Protocol
Switching Container

src/components/container.lisp

(defclass switching-container ...
  (:documentation "A simple renderable container component.

This component is like the regular CONTAINER but serves to manage a set
of components which share the same place in the UI. Therefore it provides
an implementation of RENDER which simply renders its current component.

The switching-container component class is generally used as the super
class for navigatation components and tabbed-pane like
components."))

Subclass and (defmethod render :around ...) to render navigation using (call-next-method) to render the selected component.

Protocol
Tabbed Pane

src/components/tabbed-pane.lisp

(defcomponent tabbed-pane (switching-container)
  (:documentation "Component for providing the user with a standard \"tabbed pane\" GUI widget."))

Provides a generic tabbed pane that renders a nested div split into a naviation and content box. The navigation box is a set of styled divs containing the navigation links.

Dialogs

A few convenience dialogs are provided for grabbing data from the user.

login

src/components/login.lisp

(defclass login ()
  ((username) (password) (message))
  (:documentation "Generic login (input username and password) component.

This component, which must be embedded in another component,
presents the user with a simple two fielded login form.

When the user attempts a login the action try-login is called,
try-login calls the generic function check-credentials passing it
the login component. If check-credentials returns true then the
login-successful action is called, otherwise the message slot of
the login component is set (to a generic \"bad username\"
message).

The default implementaion of login-successful simply answers t,
no default implementation of check-credentials is
provided. Developers should use sub-classes of login for which
all the required methods have been definined.")
  (:metaclass standard-component-class))
(defgeneric check-credentials (login)
  (:documentation "Returns T if LOGIN is valid."))

(defaction login-successful ((l login))
  (answer t))

src/components/user-login.lisp

(defcomponent user-login (simple-window-component status-bar-mixin)
  ((username string-field) (password password-field)))

Used by secure-application-module to provide a user login. Relevant protocol details follow.

(defmethod check-credentials ((self user-login))
  (let* ((username (value (username self)))
         (password (value (password self)))
         (user (find-application-user username)))
    (when (and user (check-user-password user password))
      user)))

(defgeneric application-find-user (application username)
  (:documentation "Find USER by USERNAME for APPLICATION."))
error

src/components/error.lisp

(defclass error-message (simple-window-component)
  ((message :accessor message :initarg :message :initform "ERROR [no message specified]"))
  (:documentation "Generic component for showing server side
 error messages.")
  (:metaclass standard-component-class))

(defclass error-component (error-message)
  ((condition :accessor error.condition :initarg :condition :initform nil)
   (backtrace :accessor error.backtrace :initarg :backtrace))
  (:documentation "Generic component for showing server side
 error conditions. Unlike ERROR-MESSAGE this component also
 attempts to display a backtrace.")
  (:metaclass standard-component-class))
message

src/components/message.lisp

(defclass info-message ()
  ((message :initarg :message :accessor message)
   (ok-text :initarg :ok-text :accessor ok-text :initform "Ok."))
  (:documentation "Component for showing a message to the user.

If the OK-TEXT slot is non-NIL component will use that as the
text for a link which, when clicked, causes the component to
answer. It follows that if OK-TEXT is NIL this component will
never answer.")
  (:metaclass standard-component-class))
option-dialog

src/components/option-dialog.lisp

(defclass option-dialog (template-component)
  ((message) (options) (confirm))
  (:default-initargs :template-name "ucw/option-dialog.tal")
  (:documentation "Component for querying the user.

The value of the slot MESSAGE is used as a general heading.

The OPTIONS slot must be an alist of (VALUE . LABEL). LABEL (a
string) will be used as the text of a link which, when clikced,
will answer VALUE.

If the CONFIRM slot is T the user will be presented with a second
OPTION-DIALOG asking the user if they are sure they want to
submit that value.")
  (:metaclass standard-component-class))

A macro to present an option dialog is provided.

(defmacro option-dialog ((message-spec &rest message-args) &body options)
  ...)

message-spec is passed to format if message-args are supplied, and used as a string literal otherwise. This does not provide a way to set the confirm property which makes the macro not so generally useful.

Forms

Reasonably useful forms library that integrates easily with TAL.

src/components/form.lisp

(defclass form-field ()
  ((validators :documentation "List of validators which will be
               applied to this field.")
   (initially-validate :documentation "When non-NIL the
                       validators will be run as soon as the page
                       is rendered.")))

(defgeneric value (form-field)
  (:documentation "The lispish translated value that represents the form-field."))

(defgeneric (setf value) (new-value form-field)
  (:documentation "Set the value of a form-field with translation to client."))

(defclass generic-html-input (form-field html-element)
  ((client-value :accessor client-value :initarg :client-value
                 :initform ""
                 :documentation "The string the client submitted along with this field.")
   (name :accessor name :initarg :name :initform nil)
   (accesskey :accessor accesskey :initarg :accesskey :initform nil)
   (tooltip :accessor tooltip :initarg :tooltip :initform nil)
   (tabindex :accessor tabindex :initarg :tabindex :initform nil))
  (:default-initargs :dom-id (js:gen-js-name-string :prefix "_ucw_")))

Fields are rendered into the extended <ucw:input yaclml tag which supports a few fancy features. The :accessor for all form elements is set to (client-value FIELD), and you should use value to access the Lisp value associated with it.

(deftag-macro <ucw:input (&attribute accessor action reader writer name id (default nil)
                          &allow-other-attributes others)
  "Generic INPUT tag replacement.

If the ACCESSOR attribute is specified then it must be a PLACE
and it's value will be used to fill the input, when the form is
submitted it will be set to the new value.

If ACTION is specefied then when the form is submitted via this
input type=\"submit\" tag the form will be eval'd. when the
submit (or image) is clicked. DEFAULT means that the ACTION
provided for this input tag will be the default action of the
form when pressing enter in a form field. If more then one, then
the latest wins.")

Validation of form fields are supported by adding to the validators list.

(defclass validator ()
  ((message :accessor message :initarg :message :initform nil)))

(defgeneric validate (field validator)
  (:documentation "Validate a form-field with a validator."))

(defgeneric javascript-check (field validator)
  (:documentation "Generate javascript code for checking FIELD against VALIDATOR.

This is the convenience entry point to generate-javascript-check,
methods defined on this generic funcition should return a list of
javascript code (as per parenscript) which tests against the
javascript variable value."))

(defgeneric javascript-invalid-handler (field validator)
  (:documentation "The javascript code body for when a field is invalid."))

(defgeneric javascript-valid-handler (field validator)
  (:documentation "Generate the javascript body for when a field is valid."))
Standard Form Fields
(defclass string-field (generic-html-input)
  ((input-size) (maxlength)))

(defclass password-field (string-field))
(defclass number-field (string-field))
(defclass integer-field (number-field))

(defclass in-field-string-field (string-field)
  ((in-field-label :documentation "This slot, if non-NIL, will be
                   used as an initial field label. An initial
                   field label is a block of text which is placed
                   inside the input element and removed as soon
                   as the user edits the field. Obviously this
                   field is overidden by an initial :client-value
                   argument.")))

(defclass textarea-field (generic-html-input)
  ((rows) (cols)))

(defclass date-field (form-field widget-component)
  ((year) (month) (day)))

(defclass dmy-date-field (date-field)
  (:documentation "Date fields which orders the inputs day/month/year"))
(defclass mdy-date-field (date-field))

(defclass select-field (generic-html-input)
  ((data-set :documentation "The values this select chooses
             from."))
  (:documentation "Form field used for selecting one value from a
  list of available options."))

(defgeneric render-value (select-field value)
  (:documentation "This function will be passed each value in the field's
   data-set and must produce the body of the corresponding
   <ucw:option tag."))

(defclass mapping-select-field (select-field)
  (:documentation "Class used when we want to chose the values of
  a certain mapping based on the keys. We render the keys in the
  select and return the corresponding value from the VALUE
  method."))

(defclass hash-table-select-field (mapping-select-field))
(defclass alist-select-field (mapping-select-field))
(defclass plist-select-field (mapping-select-field))

(defclass radio-group (generic-html-input)
  ((value-widgets)))

(defclass radio-button (generic-html-input)
  ((value)
   (group :documentation "The RADIO-GROUP this button is a part
          of."))
  (:documentation "A widget representing a single radio
  button. Should be used in conjunction with a RADIO-GROUP."))

(defmethod add-value ((group radio-group) value)
  "Adds radio-button with value to group")

(defclass checkbox-field (generic-html-input))
(defclass file-upload-field (generic-html-input))
(defclass submit-button (generic-html-input)
  ((label)))
File Upload Field

Calling value on a file-upload-field returns a mime encoded body part. (mime-part-body (value FIELD)) will return a binary stream attached to the contents of the file. The Content-Type header should be set to the MIME type of the file being uploaded.

(defgeneric mime-part-headers (mime-part)
  (:documentation "Returns an alist of the headers of MIME-PART.

The alist must be of the form (NAME . VALUE) where both NAME and
VALUE are strings."))

(defgeneric mime-part-body (mime-part)
  (:documentation "Returns the body of MIME-PART."))
Standard Validators
(defclass not-empty-validator (validator))

(defclass value-validator (validator)
  (:documentation "Validators that should only be applied if there is a value.
That is, they always succeed on nil."))

(defclass length-validator (value-validator)
  ((min-length :accessor min-length :initarg :min-length
               :initform nil)
   (max-length :accessor max-length :initarg :max-length
               :initform nil)))

(defclass string=-validator (validator)
  ((other-field :accessor other-field :initarg :other-field))
  (:documentation "Ensures that a field is string= to another one."))

(defclass regex-validator (value-validator)
  ((regex :accessor regex :initarg :regex :initform nil)))

(defclass e-mail-address-validator (regex-validator))

(defclass phone-number-validator (regex-validator))

(defclass is-a-number-validator (value-validator))
(defclass is-an-integer-validator (is-a-number-validator))

(defclass number-range-validator (is-a-number-validator)
  ((min-value :accessor min-value :initarg :min-value :initform nil)
   (max-value :accessor max-value :initarg :max-value :initform nil)))
Simple Form Helper

UCW provides a helper class for developing forms. Subclass and add the elements you wish to include in the form. A :wrapping method renders the form boilerplate and then calls your render.

(defcomponent simple-form (html-element)
  ((submit-method :accessor submit-method
                  :initform "post"
                  :initarg :submit-method)
   (dom-id :accessor dom-id
           :initform (js:gen-js-name-string :prefix "_ucw_simple_form_")
           :initarg :dom-id))
  (:default-initargs :dom-id "ucw-simple-form"))

Templates

src/components/template.lisp

Infrastructure for loading TAL templates as a view of a component.

(defclass template-component (component))
(defcomponent simple-template-component (template-component)
  ((environment :initarg :environment :initform nil)))

(defgeneric template-component-environment (component)
  (:documentation "Create the TAL environment for rendering COMPONENT's template.

Methods defined on this generic function must return a TAL
environment: a list of TAL binding sets (see the documentation
for YACLML:MAKE-STANDARD-ENVIRONMENT for details on TAL
environments.)")
  (:method-combination nconc))

(defmethod template-component-environment nconc ((component template-component))
  "Create the basic TAL environment.

Binds the symbol ucw:component to the component object itself,
also puts the object COMPONENT on the environment (after the
binding of ucw:component) so that slots are, by default,
visable."
  (make-standard-environment `((component . ,component)) component))

(defmethod render ((component template-component))
  "Render a template based component.

Calls the component's template. The name of the template is the
value returned by the generic function
template-component.template-name, the template will be rendered
in the environment returned by the generic function
template-component-environment."
  (render-template *context*
                   (template-component.template-name component)
                   (template-component-environment component)))

Subclass and override methods. simple-template-component only provides the ability to set environment variables in initarg. Subclass to provide automagic template file name generation and such.

Utility Mixin Components

Range View

src/components/range-view.lisp

(defclass range-view (template-component)
  (:default-initargs :template-name "ucw/range-view.tal")
  (:documentation "Component for showing the user a set of data one \"window\" at a time.

The data set is presented one \"window\" at a time with links to
the the first, previous, next and last window. Each window shows
at most WINDOW-SIZE elements of the data. The data is passed to
the range-view at instance creation time via the :DATA initarg.

The generic function RENDER-RANGE-VIEW-ITEM is used to render
each item of DATA.

In order to change the rendering of the single elements of a
range view developer's should create a sub class of RANGE-VIEW
and define their RENDER-RANGE-VIEW-ITEM methods on that.")
  (:metaclass standard-component-class))
(defgeneric render-range-view-item (range-view item)
  (:documentation "Render a single element of a range-view.")
  (:method ((range-view range-view) (item t))
    "Standard implementation of RENDER-RANGE-VIEW-ITEM. Simply
applies ITEM to princ (via <:as-html)."
    (declare (ignore range-view))
    (<:as-html item)))
Widget

Mixin with existing component to wrap in a div or span. This is handy for defining lightweight widgets embedded within other components.

src/components/html-element.lisp

(defclass html-element (component)
  ((css-class)
   (dom-id)
   (css-style)
   (extra-tags)
   (events))
  (:documentation "An HTML element.

HTML elements control aspects that are relevant to almost all tags.

Firstly they provide a place to store the class, id, and style of the
component. The specific render methods of the components themselves
must pass these values to whatever code is used to render the actual
tag.

Secondly, they allow javascript event handlers to be registered for a
tag. The events slot can be filled with a list of lists in the form

 (event parenscript-statement*)

For example (\"onclick\" (alert \"You clicked!\") (return nil)). If
the element has a dom-id, these event handlers are automatically
added."))

src/components/widget.lisp

(defclass widget-component (html-element)
  ()
  (:documentation "A widget which should be wrapped in a <div>."))

(defclass inline-widget-component (html-element)
  ()
  (:documentation "A widget which should be wrapped in <span> and not <div>"))

(defmethod render :wrap-around ((widget widget-component)))
(defmethod render :wrap-around ((widget inline-widget-component)))
Transactions

A mixin to provide transactions. (open-transaction component) and (close-transaction component) open and closed nested transactions. After a transaction has been closed an attempt to backtrack into a step inside the transaction will result in jumping up one level of transactions (or out of the transaction entirely if at the top level). This ensures that the transaction is only run once, naturally.

src/components/transaction-mixin.lisp

(defcomponent transaction-mixin ()
  (...))

(defmethod/cc open-transaction ((comp transaction-mixin)))
(defmethod/cc close-transaction ((comp transaction-mixin)))
Task

(defaction start ...) on subclass to run a series of actions bundled into a task.

src/components/task.lisp

(defclass task-component (standard-component)
  (...)
  (:documentation "A controller for a single task or operation to
  be performed by the user.

  A task component's START action is called as soon as the
component is instantiated. Task components do not have their own
RENDER method, in fact they have no graphical representation but
serve only to order a sequence of other components."))

(defgeneric/cc start (task)
  (:documentation "action which gets called automatically when
task-component is active. Use defaction to define your own
\"start\" action"))
Cached

src/components/cached.lisp

(defcomponent cached-component ()
  ((cached-output :accessor cached-output :initform nil
                  :documentation "A string holding the output to
                  use for this component. This string will be
                  written directly to the html stream and is
                  changed by the REFRESH-COMPONENT-OUTPUT
                  method." )
   (timeout :accessor timeout :initarg :timeout
            :documentation "An value specifying how often this
            component needs to be refreshed. The exact
            interpretation of the value depends on the type of
            caching used class."))
  (:documentation "Component which caches its output.

The component caching API is built around the generic functions
COMPONENT-DIRTY-P and REFRESH-COMPONENT-OUTPUT and a method on
RENDER, see the respective docstrings for more details.

Do not use CACHED-COMPONENT directly, use one its subclasses."))

(defgeneric component-dirty-p (component)
  (:documentation "Returns T is COMPONENT's cache is invalid."))

(defgeneric update-cache (component)
  (:documentation "Update COMPONENT's cache variables after a refresh."))

(defcomponent timeout-cache-component (cached-component)
  ((last-refresh :accessor last-refresh :initform nil
                 :documentation "The time, exrpessed as a
                 universal time, when the component was last rendered."))
  (:default-initargs
   :timeout (* 30 60 60))
  (:documentation "Render the component at most every TIMEOUT seconds."))

(defcomponent num-hits-cache-component (cached-component)
  ((hits-since-refresh :accessor hits-since-refresh
                       :initform nil
                       :documentation "Number of views since last refresh."))
  (:default-initargs :timeout 10)
  (:documentation "Render the component every TIMEOUT views."))

Subclass and override component-dirty-p to do something useful (e.g. flip mark bit when object being presented changes).

Control Flow

src/rerl/standard-component/control-flow.lisp

src/rerl/standard-action.lisp

Calling

Most of what you do in UCW will be calling components so this is a bit important. Note that calling interrupts the current control flow so if you want to render a component in place as part of another component just call render on it instead.

(defmacro call (component-type &rest component-init-args)
  "Stop the execution of the current action and pass control to
a freshly created component of type COMPONENT-TYPE.

COMPONENT-INIT-ARGS are passed directly to the underlying
make-instance call. This form will return if and when the call'd
component calls answer, the value returned by this form is
whatever the call'd component passed to answer.

Notes:

This macro assumes that the lexcial variable UCW:SELF is bound to
the calling component.")

(answer VAL) ; answer parent component ONLY IN ACTIONS

(ok SELF VAL) ; Used to answer a component anywhere and what answer
              ; expands into

(jump COMPONENT-NAME &REST ARGS) ; is similar to call, but replaces
                                 ; the current component with the new
                                 ; one and drops any backtracks (back
                                 ; button will no longer work)

(call COMPONENT-NAME &ARGS INIT-ARGS) calls COMPONENT-NAME and returns the value returned by (ok SELF RETURN-VALUE) called from within COMPONENT-NAME

Actions

Actions are methods on components. The first argument must be a component for most of UCW to work.

(defaction NAME (first ...) ...)
 ;  (roughly) expands into
(defmethod/cc NAME (first ...)
  (let ((self first))
    ...))

Self being bound in the current lexical environment is required for most UCW control flow things to work. defaction hides this from you, and was a big source of confusion for me early on (mostly "hmm, why is this not working ... where did that come from in the macroexpansion!").

Entry Points

(defentry-point url (:application APPLICATION
                     :class DISPATCHER-CLASS)
   (PARAM1 ... PARAMN) ; GET / POST vars, bound in body
  body)

An entry point is what it sounds like: a static URL matched using the mater of DISPATCHER-CLASS that enters into APPLICATION running the code in body. An example from a test program I have written follows. The entry point allows files to be streamed to user when the url audio.ucw?file=FOO is used.

(defentry-point "^(audio.ucw|)$" (:application *golf-test-app*
                                  :class regexp-dispatcher)
    (file)
  (call 'audio-file-window
   :audio-file (make-instance 'audio-file
                :type :vorbis
                :data (file->bytes (open
                                    file
                                    :element-type 'unsigned-byte)))))

Dispatching

src/rerl/standard-dispatcher.lisp

(defgeneric matcher-match (matcher application context)
  (:documentation "Abstract method for subclasses to implement a
matcher.  This method would return multiple-values according to
matcher internal nature.

No methods defined on this function may rebind *context*, nor
change CONTEXT's application. Only if the method matches the
request, it is allowed to modify CONTEXT or APPLICATION, even in
that case methods defined on this function must not modify
CONTEXT's application nor rebind *context*."))

(defgeneric handler-handle (handler application context matcher-result)
  (:documentation "Abstract function for handler classes to
implement in order to handle a request matched by relevant
matcher.

These methods may modify context as they wish since they'r
matched, request will be closed after this method is run."))

(defgeneric dispatch (dispatcher application context)
  (:documentation "Entry point into a dispatcher. Must return T
  if the context has been handled or NIL if it hasn't.

No methods defined on this function may rebind *context*, nor
change CONTEXT's application. Only if the method returns T is it
allowed to modify CONTEXT or APPLICATION, even in that case
methods defined on this function must not modify CONTEXT's
application nor rebind *context*."))
(defclass my-matcher    (abstract-matcher)    ...)
(defclass my-handler    (abstract-handler)    ...)
(defclass my-dispatcher (abstract-dispatcher my-matcher my-handler)
  ...)

Simple Dispatcher

(:documentation "This class of dispatchers avoids all of UCW's
  standard call/cc (and therefore frame/backtracking/component)
  mechanism.

Unlike all other UCW dispatchers a simple-dispatcher must not use
CALL, and must perform the rendering directly within the handler.")

Server

src/control.lisp

(defun create-server (&key
                      (backend `(,*ucw-backend-type* :host ,*ucw-backend-host*
                                 :port ,*ucw-backend-port*))
                      (applications *ucw-applications*)
                      (start-p t)
                      (server-class *ucw-server-class*)
                      (log-root-directory (truename *ucw-log-root-directory*))
                      (log-level *ucw-log-level*))
  "Creates and returns a UCW server according to SERVER-CLASS, HOST and
PORT. Affects *DEFAULT-SERVER*.

BACKEND is a list of (BACKEND-TYPE &rest INITARGS). BACKEND-TYPE
may be :HTTPD, :MOD-LISP, :ASERVE, :ARANEIDA, an existing
backend, an existing UCW server backend or :DEFAULT in which case
it attempts to return a sane default from the UCW backends loaded
and available, or any other value for which a valid MAKE-BACKEND
method has been defined. INITARGS will be passed, unmodified, to
MAKE-BACKEND.

APPLICATIONS is a list of defined applications to be loaded into the
server.

Logs are generated in verbosity defined by LOG-LEVEL and directed to
LOG-ROOT-DIRECTORY if defined."
  ...
  server) ; return server, naturally

Debugging

Inspector

/home/clinton/src/ucw/darcs/ucw_dev/src/components/ucw-inspector.lisp

(defaction call-inspector ((component component) datum)
  "Call an inspector for DATUM on the component COMPONENT."
  (call 'ucw-inspector :datum datum))

Tips

Getting dojo to load

I had some trouble getting dojo to work properly with UCW. The way that the :www-roots option for an application works is a bit confusing, and it is unforgiving if you mess the pathname up. A directory must have a / at the end, and the directory you are serving must also have the / (which is counterintuitive given the behavior of most unix things that don't want the / at the end of the name).

:www-roots (list '("dojo/" .
                   #P"/home/clinton/src/ucw/darcs/ucw_dev/wwwroot/dojo/"))

Specials Bound During Rendering

The current request context is bound to ucw:*context*, and the current component is bound to ucw:*current-component* in the dynamic extent of render.

Printing to the yaclml stream

Occasionally it can be useful to do something like write a byte array as an ascii string to the client. Inside of render the variable yaclml:*yaclml-stream* is bound to the stream that you can write to if you wish to have content interspersed with yaclml tags.

Valid XHTML 1.0! [ Viewable With Any Browser
	] [ Powered by Debian ] [ Hosted by HCoop] [ FSF Associate Member ]

Last Modified: January 21, 2013