1 #title Roadmap to UCW Codebase
5 [[http://common-lisp.net/project/ucw/][UnCommon Web]] is a very powerful and mature web framework for Common
6 Lisp, but is a bit difficult to learn. It is documented
7 extensively--in the form of docstrings. These are extremely helpful
8 once you've figured out the rough structure of UCW, but they are of no
9 help when first learning unless you just read most of the source. I
10 ended up having to do that, and after some urging along by folks in
11 =#ucw= I decided to clean up my planner notes and publish them for
14 The roadmap is presented with major sections ordered in a logical
15 order for learning the framework. The sections are ordered internally
16 in order of most immediately useful to least, but it may be worth
17 hopping between major sections before reading all of the details. I
18 have used abridged class definitions and docstrings with occasional
19 commentary to clarify things.
25 Applications are a bundle of entry points. The base class is,
26 naturally, =standard-application=, but you should instead derive your
27 application class from =modular-application= and any standard or custom
28 application mixins you find useful.
30 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-classes.lisp][src/rerl/standard-classes.lisp]]
33 (defclass standard-application (application)
34 ((url-prefix :initarg :url-prefix
35 :documentation "A string specifying the
36 start (prefix) of all the urls this app should handle.
38 This value is used by the standard-server to decide what app a
39 particular request is aimed at and for generating links to
40 actions within the app. ")
41 (www-roots :initarg :www-roots
42 :documentation "A list of directories (pathname
43 specifiers) or cons-cell (URL-subdir . pathname) to use when looking for static files.")
44 (dispatchers :initarg :dispatchers
45 :documentation "A list of request
46 dispatchers. The user supplied list of dispatchers is extended
47 with other dispatchers that are required for UCW to function
48 properly (action-dispatcher, a parenscript-dispatcher, etc). If
49 you want full control over the active dispatchers use the (setf
50 application.dispatchers) accessor or, if you want control over
51 the order of the dispathcers, (slot-value instance
53 (:documentation "The default UCW application class."))
56 [[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]]
59 (defclass modular-application-mixin ()
61 (:documentation "Superclass for all application mixins."))
63 (defclass modular-application (standard-application modular-application-mixin)
69 [[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]]
72 (defclass cookie-session-application-module (modular-application-mixin)
73 (:documentation "Class for applications which use cookies for sesion tracking.
75 Cookie session applications work exactly like
76 standard-applications except that when the session is not found
77 using the standard mechanisms the id is looked for in a cookie."))
80 This is the most useful of the application components. It makes your
81 application urls readable by stashing the session id into a cookie
82 rather than as a set of long and ugly GET parameters.
86 [[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]]
89 (defclass l10n-application-module (modular-application-mixin)
90 (:documentation "Application class which can handle l10n requests."))
95 [[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]]
98 (defclass secure-application-module (modular-application-mixin)
100 "Mixin class for applications which require authorized access.
101 Concrete application must specialize the following methods:
102 APPLICATION-FIND-USER (APPLICATION USERNAME)
103 APPLICATION-CHECK-PASSWORD (APPLICATION USER PASSWORD)
104 APPLICATION-AUTHORIZE-CALLZE-CALL (APPLICATION USER FROM-COMPONENT TO-COMPONENT)."))
109 A component is a special class that handles the complexities of
110 continuation suspension and such for you. New components are derived
111 from the existing ones by using =defcomponent= instead of =defclass=. This
112 adds a few extra slot and class options, and ensures that the proper
115 [[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]]
118 (defmacro defcomponent (name supers slots &rest options)
119 "Macro for defining a component class.
121 This macro is used to create component classes and provides
122 options for easily creating the methods which often accompany a
123 component definition.
125 NAME, SUPERS and SLOTS as treated as per defclass. The following
126 extra options are allowed:
128 (:ENTRY-POINT url (&key application class)) - Define an
129 entry-point on the url URL which simply calls an instance of
130 this component. Any request parameters passed to the entry-point
131 are used to initialize the slots in the component. This option
132 may appear multiple times.
134 (:DEFAULT-BACKTRACK function-designator) - Unless the slots
135 already have a :backtrack option FUNCTION-DESIGNATOR is
136 added. As with the 'regular' :backtrack options if you pass T
137 here it is assumed to mean #'IDENTITY.
139 (:RENDER (&optional COMPONENT) &body BODY) - Generate a render
140 method specialized to COMPONENT. COMPONENT may be a symbol, in
141 which case the method will be specialized on the componnet
142 class. If COMPONNET is omited the component is bound to a
143 variable with the same name as the class.
145 (:ACTION &optional NAME) - Generate a defaction form named
146 NAME (which defaults to the name of the component) which simply
147 CALL's this component class passing all the arguments passed to
148 the action as initargs.")
150 ;;; Extra Slot Options
151 "Other than the initargs for standard slots the following
152 options can be passed to component slots:
154 :backtrack [ T | NIL | FUNCTION-NAME ] - Specify that this slot
155 should be backtracked (or not if NIL is passed as the value). If
156 the value is neither T nor NIL then it must be a function which
157 will be used as the copyer.
159 :component [ TYPE | ( TYPE &rest INITARGS ) ] - Specify that this
160 slot is actually a nested component of type TYPE. When instances
161 of the class are created this slot will be set to an instance of
162 type TYPE and it's place will be set to this slot. If a list is
163 passed to :component then TYPE (which isn't evaluated) will be
164 passed as the first argument to make-instance. The INITARGS will
165 be eval'd and apply'd to make-instance. The result of this call
166 to make-instance will be used as the effective component
172 A window-component represents a top level browser window, naturally.
174 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/window.lisp][src/components/window.lisp]]
177 (defclass window-component ()
180 (defclass simple-window-component (window-component)
183 (javascript :documentation "List of javascript includes.
185 Each element must be a list whose first value is either the
188 (:SRC url) - writes <script src=\"URL\"></script> tag.
189 (:JS form) - equivalent to (:SCRIPT (js:js* form))
190 (:SCRIPT string) - write <script>STRING</script>.
192 The elements will be rendered in order.")
196 =window-component= could be useful for doing things like dumping binary
197 data to the user, or just deriving your own funky top level window
200 =simple-window-component= is the easiest for displaying standard
201 webpage. It provides a wrapping method on render that displays the
202 html boilerplate based on your component slot values which is what one
203 wants most of the time. The initargs to =simple-window-component= have
204 the same names as the slots.
208 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/status-bar.lisp][src/components/status-bar.lisp]]
210 There is a generic status bar interface. Messages severity is one of
211 =(:error :warn :info)=. Note that the default status bar render method
212 just shows a div with status messages. A derivative could be defined
213 to insert messages into the browser status bar.
216 (defcomponent status-bar ()
217 ((messages :documentation "An ALIST of the messages to
218 show. Each element is a cons of the form (SEVERITY .
219 MESSAGE). SEVERITY is one of :ERROR, :WARN, :INFO and MESSAGE is
220 a string which will be html-escaped.")
222 (:documentation "Stateless status bar to display messages."))
224 (defgeneric add-message (status-bar msg &key severity &allow-other-keys)
225 (:documentation "Add the message text MSG to STATUS-BAR with
226 severity SEVERITY."))
230 (defcomponent status-bar-mixin ()
231 ((status-bar :accessor status-bar
233 :component (status-bar))))
235 (defmethod show-status-bar ((win status-bar-mixin))
236 (render (status-bar win)))
238 (defgeneric show-message (msg &key severity &allow-other-keys)
239 (:documentation "Show a message in the status bar. Only works if
240 current window is a status-bar-mixin"))
245 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/redirect.lisp][src/components/redirect.lisp]]
248 (defclass redirect-component ()
249 ((target :accessor target :initarg :target))
250 (:metaclass standard-component-class)
251 (:documentation "Send a client redirect.
253 This component, which must be used as a window-component,
254 redirects the client to the url specified in the target slot. A
255 302 (as opposed to 303) response code is sent to ensure
256 compatability with older browsers.
258 The redirect component never answers."))
261 There is also a =meta-refresh= procedure.
264 (defun/cc meta-refresh ()
265 "Cause a meta-refresh (a freshly got (GET) url) at this point.
266 This is useful in order to have a GET url after a form POST's
267 actions have completed running. The user can then refresh to his
273 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/container.lisp][src/components/container.lisp]]
276 (defclass container ()
278 (:metaclass standard-component-class)
279 (:documentation "Allow multiple components to share the same place.
281 The container component serves to manage a set of components.
282 It does not provide any render impementation, which is the
283 resposibility of the subclasses (e.g. switching-container or
286 Each contained component has a \"key\" associated with it which
287 is used to retrieve a particular component. Keys are compared with
290 The :contents inintarg, if provided, must be either a list of (key .
291 component) or a list of components. In the latter case it will
292 be converted into (component . component) form."))
298 - =find-component CONTAINER KEY=
300 - =(setf find-component CONTAINER KEY) COMPONENT= ->
301 =add-component CONTAINER COMPONENT KEY=
303 **** Switching Container
305 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/container.lisp][src/components/container.lisp]]
308 (defclass switching-container ...
309 (:documentation "A simple renderable container component.
311 This component is like the regular CONTAINER but serves to manage a set
312 of components which share the same place in the UI. Therefore it provides
313 an implementation of RENDER which simply renders its current component.
315 The switching-container component class is generally used as the super
316 class for navigatation components and tabbed-pane like
320 Subclass and =(defmethod render :around ...)= to render navigation using
321 =(call-next-method)= to render the selected component.
325 - =container.current-component COMPONENT=
326 - =(setf container.current-component CONTAINER) COMPONENT=
330 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/tabbed-pane.lisp][src/components/tabbed-pane.lisp]]
333 (defcomponent tabbed-pane (switching-container)
334 (:documentation "Component for providing the user with a standard \"tabbed pane\" GUI widget."))
337 Provides a generic tabbed pane that renders a nested div split into a
338 naviation and content box. The navigation box is a set of styled divs
339 containing the navigation links.
343 A few convenience dialogs are provided for grabbing data from the
348 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/login.lisp][src/components/login.lisp]]
352 ((username) (password) (message))
353 (:documentation "Generic login (input username and password) component.
355 This component, which must be embedded in another component,
356 presents the user with a simple two fielded login form.
358 When the user attempts a login the action try-login is called,
359 try-login calls the generic function check-credentials passing it
360 the login component. If check-credentials returns true then the
361 login-successful action is called, otherwise the message slot of
362 the login component is set (to a generic \"bad username\"
365 The default implementaion of login-successful simply answers t,
366 no default implementation of check-credentials is
367 provided. Developers should use sub-classes of login for which
368 all the required methods have been definined.")
369 (:metaclass standard-component-class))
373 (defgeneric check-credentials (login)
374 (:documentation "Returns T if LOGIN is valid."))
376 (defaction login-successful ((l login))
380 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/user-login.lisp][src/components/user-login.lisp]]
383 (defcomponent user-login (simple-window-component status-bar-mixin)
384 ((username string-field) (password password-field)))
387 Used by =secure-application-module= to provide a user login. Relevant
388 protocol details follow.
391 (defmethod check-credentials ((self user-login))
392 (let* ((username (value (username self)))
393 (password (value (password self)))
394 (user (find-application-user username)))
395 (when (and user (check-user-password user password))
398 (defgeneric application-find-user (application username)
399 (:documentation "Find USER by USERNAME for APPLICATION."))
404 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/error.lisp][src/components/error.lisp]]
407 (defclass error-message (simple-window-component)
408 ((message :accessor message :initarg :message :initform "ERROR [no message specified]"))
409 (:documentation "Generic component for showing server side
411 (:metaclass standard-component-class))
413 (defclass error-component (error-message)
414 ((condition :accessor error.condition :initarg :condition :initform nil)
415 (backtrace :accessor error.backtrace :initarg :backtrace))
416 (:documentation "Generic component for showing server side
417 error conditions. Unlike ERROR-MESSAGE this component also
418 attempts to display a backtrace.")
419 (:metaclass standard-component-class))
424 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/message.lisp][src/components/message.lisp]]
427 (defclass info-message ()
428 ((message :initarg :message :accessor message)
429 (ok-text :initarg :ok-text :accessor ok-text :initform "Ok."))
430 (:documentation "Component for showing a message to the user.
432 If the OK-TEXT slot is non-NIL component will use that as the
433 text for a link which, when clicked, causes the component to
434 answer. It follows that if OK-TEXT is NIL this component will
436 (:metaclass standard-component-class))
441 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/option-dialog.lisp][src/components/option-dialog.lisp]]
444 (defclass option-dialog (template-component)
445 ((message) (options) (confirm))
446 (:default-initargs :template-name "ucw/option-dialog.tal")
447 (:documentation "Component for querying the user.
449 The value of the slot MESSAGE is used as a general heading.
451 The OPTIONS slot must be an alist of (VALUE . LABEL). LABEL (a
452 string) will be used as the text of a link which, when clikced,
455 If the CONFIRM slot is T the user will be presented with a second
456 OPTION-DIALOG asking the user if they are sure they want to
458 (:metaclass standard-component-class))
461 A macro to present an option dialog is provided.
464 (defmacro option-dialog ((message-spec &rest message-args) &body options)
468 =message-spec= is passed to =format= if =message-args= are supplied, and
469 used as a string literal otherwise. This does not provide a way to set
470 the confirm property which makes the macro not so generally useful.
474 Reasonably useful forms library that integrates easily with TAL.
476 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/form.lisp][src/components/form.lisp]]
479 (defclass form-field ()
480 ((validators :documentation "List of validators which will be
481 applied to this field.")
482 (initially-validate :documentation "When non-NIL the
483 validators will be run as soon as the page
486 (defgeneric value (form-field)
487 (:documentation "The lispish translated value that represents the form-field."))
489 (defgeneric (setf value) (new-value form-field)
490 (:documentation "Set the value of a form-field with translation to client."))
492 (defclass generic-html-input (form-field html-element)
493 ((client-value :accessor client-value :initarg :client-value
495 :documentation "The string the client submitted along with this field.")
496 (name :accessor name :initarg :name :initform nil)
497 (accesskey :accessor accesskey :initarg :accesskey :initform nil)
498 (tooltip :accessor tooltip :initarg :tooltip :initform nil)
499 (tabindex :accessor tabindex :initarg :tabindex :initform nil))
500 (:default-initargs :dom-id (js:gen-js-name-string :prefix "_ucw_")))
503 Fields are rendered into the extended =<ucw:input= yaclml tag which
504 supports a few fancy features. The =:accessor= for all form elements is
505 set to =(client-value FIELD)=, and you should use =value= to access the
506 Lisp value associated with it.
509 (deftag-macro <ucw:input (&attribute accessor action reader writer name id (default nil)
510 &allow-other-attributes others)
511 "Generic INPUT tag replacement.
513 If the ACCESSOR attribute is specified then it must be a PLACE
514 and it's value will be used to fill the input, when the form is
515 submitted it will be set to the new value.
517 If ACTION is specefied then when the form is submitted via this
518 input type=\"submit\" tag the form will be eval'd. when the
519 submit (or image) is clicked. DEFAULT means that the ACTION
520 provided for this input tag will be the default action of the
521 form when pressing enter in a form field. If more then one, then
525 Validation of form fields are supported by adding to the validators
529 (defclass validator ()
530 ((message :accessor message :initarg :message :initform nil)))
532 (defgeneric validate (field validator)
533 (:documentation "Validate a form-field with a validator."))
535 (defgeneric javascript-check (field validator)
536 (:documentation "Generate javascript code for checking FIELD against VALIDATOR.
538 This is the convenience entry point to generate-javascript-check,
539 methods defined on this generic funcition should return a list of
540 javascript code (as per parenscript) which tests against the
541 javascript variable value."))
543 (defgeneric javascript-invalid-handler (field validator)
544 (:documentation "The javascript code body for when a field is invalid."))
546 (defgeneric javascript-valid-handler (field validator)
547 (:documentation "Generate the javascript body for when a field is valid."))
550 **** Standard Form Fields
553 (defclass string-field (generic-html-input)
554 ((input-size) (maxlength)))
556 (defclass password-field (string-field))
557 (defclass number-field (string-field))
558 (defclass integer-field (number-field))
560 (defclass in-field-string-field (string-field)
561 ((in-field-label :documentation "This slot, if non-NIL, will be
562 used as an initial field label. An initial
563 field label is a block of text which is placed
564 inside the input element and removed as soon
565 as the user edits the field. Obviously this
566 field is overidden by an initial :client-value
569 (defclass textarea-field (generic-html-input)
572 (defclass date-field (form-field widget-component)
573 ((year) (month) (day)))
575 (defclass dmy-date-field (date-field)
576 (:documentation "Date fields which orders the inputs day/month/year"))
577 (defclass mdy-date-field (date-field))
579 (defclass select-field (generic-html-input)
580 ((data-set :documentation "The values this select chooses
582 (:documentation "Form field used for selecting one value from a
583 list of available options."))
585 (defgeneric render-value (select-field value)
586 (:documentation "This function will be passed each value in the field's
587 data-set and must produce the body of the corresponding
590 (defclass mapping-select-field (select-field)
591 (:documentation "Class used when we want to chose the values of
592 a certain mapping based on the keys. We render the keys in the
593 select and return the corresponding value from the VALUE
596 (defclass hash-table-select-field (mapping-select-field))
597 (defclass alist-select-field (mapping-select-field))
598 (defclass plist-select-field (mapping-select-field))
600 (defclass radio-group (generic-html-input)
603 (defclass radio-button (generic-html-input)
605 (group :documentation "The RADIO-GROUP this button is a part
607 (:documentation "A widget representing a single radio
608 button. Should be used in conjunction with a RADIO-GROUP."))
610 (defmethod add-value ((group radio-group) value)
611 "Adds radio-button with value to group")
613 (defclass checkbox-field (generic-html-input))
614 (defclass file-upload-field (generic-html-input))
615 (defclass submit-button (generic-html-input)
619 ***** File Upload Field
621 Calling =value= on a =file-upload-field= returns a mime encoded body
622 part. =(mime-part-body (value FIELD))= will return a **binary stream**
623 attached to the contents of the file. The =Content-Type= header should
624 be set to the MIME type of the file being uploaded.
627 (defgeneric mime-part-headers (mime-part)
628 (:documentation "Returns an alist of the headers of MIME-PART.
630 The alist must be of the form (NAME . VALUE) where both NAME and
631 VALUE are strings."))
633 (defgeneric mime-part-body (mime-part)
634 (:documentation "Returns the body of MIME-PART."))
637 **** Standard Validators
640 (defclass not-empty-validator (validator))
642 (defclass value-validator (validator)
643 (:documentation "Validators that should only be applied if there is a value.
644 That is, they always succeed on nil."))
646 (defclass length-validator (value-validator)
647 ((min-length :accessor min-length :initarg :min-length
649 (max-length :accessor max-length :initarg :max-length
652 (defclass string=-validator (validator)
653 ((other-field :accessor other-field :initarg :other-field))
654 (:documentation "Ensures that a field is string= to another one."))
656 (defclass regex-validator (value-validator)
657 ((regex :accessor regex :initarg :regex :initform nil)))
659 (defclass e-mail-address-validator (regex-validator))
661 (defclass phone-number-validator (regex-validator))
663 (defclass is-a-number-validator (value-validator))
664 (defclass is-an-integer-validator (is-a-number-validator))
666 (defclass number-range-validator (is-a-number-validator)
667 ((min-value :accessor min-value :initarg :min-value :initform nil)
668 (max-value :accessor max-value :initarg :max-value :initform nil)))
671 **** Simple Form Helper
673 UCW provides a helper class for developing forms. Subclass and add the
674 elements you wish to include in the form. A =:wrapping= method renders
675 the form boilerplate and then calls your =render=.
678 (defcomponent simple-form (html-element)
679 ((submit-method :accessor submit-method
681 :initarg :submit-method)
682 (dom-id :accessor dom-id
683 :initform (js:gen-js-name-string :prefix "_ucw_simple_form_")
685 (:default-initargs :dom-id "ucw-simple-form"))
690 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/template.lisp][src/components/template.lisp]]
692 Infrastructure for loading TAL templates as a view of a component.
695 (defclass template-component (component))
696 (defcomponent simple-template-component (template-component)
697 ((environment :initarg :environment :initform nil)))
699 (defgeneric template-component-environment (component)
700 (:documentation "Create the TAL environment for rendering COMPONENT's template.
702 Methods defined on this generic function must return a TAL
703 environment: a list of TAL binding sets (see the documentation
704 for YACLML:MAKE-STANDARD-ENVIRONMENT for details on TAL
706 (:method-combination nconc))
708 (defmethod template-component-environment nconc ((component template-component))
709 "Create the basic TAL environment.
711 Binds the symbol ucw:component to the component object itself,
712 also puts the object COMPONENT on the environment (after the
713 binding of ucw:component) so that slots are, by default,
715 (make-standard-environment `((component . ,component)) component))
717 (defmethod render ((component template-component))
718 "Render a template based component.
720 Calls the component's template. The name of the template is the
721 value returned by the generic function
722 template-component.template-name, the template will be rendered
723 in the environment returned by the generic function
724 template-component-environment."
725 (render-template *context*
726 (template-component.template-name component)
727 (template-component-environment component)))
731 Subclass and override methods. =simple-template-component= only provides
732 the ability to set environment variables in initarg. Subclass to
733 provide automagic template file name generation and such.
735 *** Utility Mixin Components
739 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/range-view.lisp][src/components/range-view.lisp]]
742 (defclass range-view (template-component)
743 (:default-initargs :template-name "ucw/range-view.tal")
744 (:documentation "Component for showing the user a set of data one \"window\" at a time.
746 The data set is presented one \"window\" at a time with links to
747 the the first, previous, next and last window. Each window shows
748 at most WINDOW-SIZE elements of the data. The data is passed to
749 the range-view at instance creation time via the :DATA initarg.
751 The generic function RENDER-RANGE-VIEW-ITEM is used to render
754 In order to change the rendering of the single elements of a
755 range view developer's should create a sub class of RANGE-VIEW
756 and define their RENDER-RANGE-VIEW-ITEM methods on that.")
757 (:metaclass standard-component-class))
761 (defgeneric render-range-view-item (range-view item)
762 (:documentation "Render a single element of a range-view.")
763 (:method ((range-view range-view) (item t))
764 "Standard implementation of RENDER-RANGE-VIEW-ITEM. Simply
765 applies ITEM to princ (via <:as-html)."
766 (declare (ignore range-view))
772 Mixin with existing component to wrap in a div or span. This is handy
773 for defining lightweight widgets embedded within other components.
775 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/html-element.lisp][src/components/html-element.lisp]]
778 (defclass html-element (component)
784 (:documentation "An HTML element.
786 HTML elements control aspects that are relevant to almost all tags.
788 Firstly they provide a place to store the class, id, and style of the
789 component. The specific render methods of the components themselves
790 must pass these values to whatever code is used to render the actual
793 Secondly, they allow javascript event handlers to be registered for a
794 tag. The events slot can be filled with a list of lists in the form
796 (event parenscript-statement*)
798 For example (\"onclick\" (alert \"You clicked!\") (return nil)). If
799 the element has a dom-id, these event handlers are automatically
803 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/widget.lisp][src/components/widget.lisp]]
806 (defclass widget-component (html-element)
808 (:documentation "A widget which should be wrapped in a <div>."))
810 (defclass inline-widget-component (html-element)
812 (:documentation "A widget which should be wrapped in <span> and not <div>"))
814 (defmethod render :wrap-around ((widget widget-component)))
815 (defmethod render :wrap-around ((widget inline-widget-component)))
820 A mixin to provide transactions. =(open-transaction component)= and
821 =(close-transaction component)= open and closed nested
822 transactions. After a transaction has been closed an attempt to
823 backtrack into a step inside the transaction will result in jumping up
824 one level of transactions (or out of the transaction entirely if at
825 the top level). This ensures that the transaction is only run once,
828 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/transaction-mixin.lisp][src/components/transaction-mixin.lisp]]
831 (defcomponent transaction-mixin ()
834 (defmethod/cc open-transaction ((comp transaction-mixin)))
835 (defmethod/cc close-transaction ((comp transaction-mixin)))
840 =(defaction start ...)= on subclass to run a series of actions bundled
843 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/task.lisp][src/components/task.lisp]]
846 (defclass task-component (standard-component)
848 (:documentation "A controller for a single task or operation to
849 be performed by the user.
851 A task component's START action is called as soon as the
852 component is instantiated. Task components do not have their own
853 RENDER method, in fact they have no graphical representation but
854 serve only to order a sequence of other components."))
856 (defgeneric/cc start (task)
857 (:documentation "action which gets called automatically when
858 task-component is active. Use defaction to define your own
864 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/cached.lisp][src/components/cached.lisp]]
867 (defcomponent cached-component ()
868 ((cached-output :accessor cached-output :initform nil
869 :documentation "A string holding the output to
870 use for this component. This string will be
871 written directly to the html stream and is
872 changed by the REFRESH-COMPONENT-OUTPUT
874 (timeout :accessor timeout :initarg :timeout
875 :documentation "An value specifying how often this
876 component needs to be refreshed. The exact
877 interpretation of the value depends on the type of
878 caching used class."))
879 (:documentation "Component which caches its output.
881 The component caching API is built around the generic functions
882 COMPONENT-DIRTY-P and REFRESH-COMPONENT-OUTPUT and a method on
883 RENDER, see the respective docstrings for more details.
885 Do not use CACHED-COMPONENT directly, use one its subclasses."))
887 (defgeneric component-dirty-p (component)
888 (:documentation "Returns T is COMPONENT's cache is invalid."))
890 (defgeneric update-cache (component)
891 (:documentation "Update COMPONENT's cache variables after a refresh."))
893 (defcomponent timeout-cache-component (cached-component)
894 ((last-refresh :accessor last-refresh :initform nil
895 :documentation "The time, exrpessed as a
896 universal time, when the component was last rendered."))
898 :timeout (* 30 60 60))
899 (:documentation "Render the component at most every TIMEOUT seconds."))
901 (defcomponent num-hits-cache-component (cached-component)
902 ((hits-since-refresh :accessor hits-since-refresh
904 :documentation "Number of views since last refresh."))
905 (:default-initargs :timeout 10)
906 (:documentation "Render the component every TIMEOUT views."))
909 Subclass and override =component-dirty-p= to do something useful
910 (e.g. flip mark bit when object being presented changes).
914 [[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]]
916 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-action.lisp][src/rerl/standard-action.lisp]]
920 Most of what you do in UCW will be calling components so this is a bit
921 important. Note that calling interrupts the current control flow so if
922 you want to render a component in place as part of another component
923 just call =render= on it instead.
926 (defmacro call (component-type &rest component-init-args)
927 "Stop the execution of the current action and pass control to
928 a freshly created component of type COMPONENT-TYPE.
930 COMPONENT-INIT-ARGS are passed directly to the underlying
931 make-instance call. This form will return if and when the call'd
932 component calls answer, the value returned by this form is
933 whatever the call'd component passed to answer.
937 This macro assumes that the lexcial variable UCW:SELF is bound to
938 the calling component.")
940 (answer VAL) ; answer parent component ONLY IN ACTIONS
942 (ok SELF VAL) ; Used to answer a component anywhere and what answer
945 (jump COMPONENT-NAME &REST ARGS) ; is similar to call, but replaces
946 ; the current component with the new
947 ; one and drops any backtracks (back
948 ; button will no longer work)
951 =(call COMPONENT-NAME &ARGS INIT-ARGS)= calls =COMPONENT-NAME= and returns
952 the value returned by =(ok SELF RETURN-VALUE)= called from within
957 Actions are methods on components. The first argument **must** be a
958 component for most of UCW to work.
961 (defaction NAME (first ...) ...)
962 ; (roughly) expands into
963 (defmethod/cc NAME (first ...)
968 =Self= being bound in the current lexical environment is required for
969 most UCW control flow things to work. =defaction= hides this from you,
970 and was a big source of confusion for me early on (mostly "hmm, why is
971 this not working ... where did that come from in the
977 (defentry-point url (:application APPLICATION
978 :class DISPATCHER-CLASS)
979 (PARAM1 ... PARAMN) ; GET / POST vars, bound in body
983 An entry point is what it sounds like: a static URL matched using the
984 mater of =DISPATCHER-CLASS= that enters into =APPLICATION= running the
985 code in =body=. An example from a test program I have written
986 follows. The entry point allows files to be streamed to user when the
987 url audio.ucw?file=FOO is used.
990 (defentry-point "^(audio.ucw|)$" (:application *golf-test-app*
991 :class regexp-dispatcher)
993 (call 'audio-file-window
994 :audio-file (make-instance 'audio-file
996 :data (file->bytes (open
998 :element-type 'unsigned-byte)))))
1003 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-dispatcher.lisp][src/rerl/standard-dispatcher.lisp]]
1006 (defgeneric matcher-match (matcher application context)
1007 (:documentation "Abstract method for subclasses to implement a
1008 matcher. This method would return multiple-values according to
1009 matcher internal nature.
1011 No methods defined on this function may rebind *context*, nor
1012 change CONTEXT's application. Only if the method matches the
1013 request, it is allowed to modify CONTEXT or APPLICATION, even in
1014 that case methods defined on this function must not modify
1015 CONTEXT's application nor rebind *context*."))
1017 (defgeneric handler-handle (handler application context matcher-result)
1018 (:documentation "Abstract function for handler classes to
1019 implement in order to handle a request matched by relevant
1022 These methods may modify context as they wish since they'r
1023 matched, request will be closed after this method is run."))
1025 (defgeneric dispatch (dispatcher application context)
1026 (:documentation "Entry point into a dispatcher. Must return T
1027 if the context has been handled or NIL if it hasn't.
1029 No methods defined on this function may rebind *context*, nor
1030 change CONTEXT's application. Only if the method returns T is it
1031 allowed to modify CONTEXT or APPLICATION, even in that case
1032 methods defined on this function must not modify CONTEXT's
1033 application nor rebind *context*."))
1037 (defclass my-matcher (abstract-matcher) ...)
1038 (defclass my-handler (abstract-handler) ...)
1039 (defclass my-dispatcher (abstract-dispatcher my-matcher my-handler)
1043 *** Simple Dispatcher
1046 (:documentation "This class of dispatchers avoids all of UCW's
1047 standard call/cc (and therefore frame/backtracking/component)
1050 Unlike all other UCW dispatchers a simple-dispatcher must not use
1051 CALL, and must perform the rendering directly within the handler.")
1056 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/control.lisp][src/control.lisp]]
1059 (defun create-server (&key
1060 (backend `(,*ucw-backend-type* :host ,*ucw-backend-host*
1061 :port ,*ucw-backend-port*))
1062 (applications *ucw-applications*)
1064 (server-class *ucw-server-class*)
1065 (log-root-directory (truename *ucw-log-root-directory*))
1066 (log-level *ucw-log-level*))
1067 "Creates and returns a UCW server according to SERVER-CLASS, HOST and
1068 PORT. Affects *DEFAULT-SERVER*.
1070 BACKEND is a list of (BACKEND-TYPE &rest INITARGS). BACKEND-TYPE
1071 may be :HTTPD, :MOD-LISP, :ASERVE, :ARANEIDA, an existing
1072 backend, an existing UCW server backend or :DEFAULT in which case
1073 it attempts to return a sane default from the UCW backends loaded
1074 and available, or any other value for which a valid MAKE-BACKEND
1075 method has been defined. INITARGS will be passed, unmodified, to
1078 APPLICATIONS is a list of defined applications to be loaded into the
1081 Logs are generated in verbosity defined by LOG-LEVEL and directed to
1082 LOG-ROOT-DIRECTORY if defined."
1084 server) ; return server, naturally
1091 [[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]]
1094 (defaction call-inspector ((component component) datum)
1095 "Call an inspector for DATUM on the component COMPONENT."
1096 (call 'ucw-inspector :datum datum))
1101 ** Getting dojo to load
1103 I had some trouble getting dojo to work properly with UCW. The way
1104 that the =:www-roots= option for an application works is a bit
1105 confusing, and it is unforgiving if you mess the pathname up. A
1106 directory **must** have a =/= at the end, and the directory you are serving
1107 must also have the =/= (which is counterintuitive given the behavior of
1108 most unix things that don't want the =/= at the end of the name).
1111 :www-roots (list '("dojo/" .
1112 #P"/home/clinton/src/ucw/darcs/ucw_dev/wwwroot/dojo/"))
1115 ** Specials Bound During Rendering
1117 The current request context is bound to =ucw:*context*=, and the current
1118 component is bound to =ucw:*current-component*= in the dynamic extent of
1121 ** Printing to the yaclml stream
1123 Occasionally it can be useful to do something like write a byte array
1124 as an ascii string to the client. Inside of =render= the variable
1125 =yaclml:*yaclml-stream*= is bound to the stream that you can write to if
1126 you wish to have content interspersed with yaclml tags.