#title Roadmap to UCW Codebase * Abstract [[http://common-lisp.net/project/ucw/][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. [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-classes.lisp][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.")) [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/modular-application/modular-application.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/modular-application/cookie-module.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/modular-application/l10n-module.lisp][src/rerl/modular-application/l10n-module.lisp]] (defclass l10n-application-module (modular-application-mixin) (:documentation "Application class which can handle l10n requests.")) *** Secure [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/modular-application/security-module.lisp][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. [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-component/standard-component.lisp][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. [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/window.lisp][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 tag. (:JS form) - equivalent to (:SCRIPT (js:js* form)) (:SCRIPT string) - write . 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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/status-bar.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/redirect.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/container.lisp][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 - =child-components= - =find-component CONTAINER KEY= - =remove-component= - =(setf find-component CONTAINER KEY) COMPONENT= -> =add-component CONTAINER COMPONENT KEY= **** Switching Container [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/container.lisp][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 - =container.current-component COMPONENT= - =(setf container.current-component CONTAINER) COMPONENT= **** Tabbed Pane [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/tabbed-pane.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/login.lisp][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)) [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/user-login.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/error.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/message.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/option-dialog.lisp][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. [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/form.lisp][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 = (deftag-macro 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 ***** 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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/template.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/range-view.lisp][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. [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/html-element.lisp][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.")) [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/widget.lisp][src/components/widget.lisp]] (defclass widget-component (html-element) () (:documentation "A widget which should be wrapped in a
.")) (defclass inline-widget-component (html-element) () (:documentation "A widget which should be wrapped in and not
")) (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. [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/transaction-mixin.lisp][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. [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/task.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/cached.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-component/control-flow.lisp][src/rerl/standard-component/control-flow.lisp]] [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-action.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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-dispatcher.lisp][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 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/control.lisp][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 [[pos:///home/clinton/src/ucw/darcs/ucw_dev/src/components/ucw-inspector.lisp#399][/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.