More misguided Political Ranting from the Author
[clinton/website/src/unknownlamer.org.git] / UCWNotes.muse
1 #title Roadmap to UCW Codebase
2
3 * Abstract
4
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
12 public consumption.
13
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.
20
21 * Roadmap
22
23 ** Applications
24
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.
29
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]]
31
32 <src lang="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.
37
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
52 'dispatchers)."))
53 (:documentation "The default UCW application class."))
54 </src>
55
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]]
57
58 <src lang="lisp">
59 (defclass modular-application-mixin ()
60 ()
61 (:documentation "Superclass for all application mixins."))
62
63 (defclass modular-application (standard-application modular-application-mixin)
64 ...)
65 </src>
66
67 *** Cookie
68
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]]
70
71 <src lang="lisp">
72 (defclass cookie-session-application-module (modular-application-mixin)
73 (:documentation "Class for applications which use cookies for sesion tracking.
74
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."))
78 </src>
79
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.
83
84 *** L10n
85
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]]
87
88 <src lang="lisp">
89 (defclass l10n-application-module (modular-application-mixin)
90 (:documentation "Application class which can handle l10n requests."))
91 </src>
92
93 *** Secure
94
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]]
96
97 <src lang="lisp">
98 (defclass secure-application-module (modular-application-mixin)
99 (:documentation
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)."))
105 </src>
106
107 ** Components
108
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
113 metaclass is set.
114
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]]
116
117 <src lang="lisp">
118 (defmacro defcomponent (name supers slots &rest options)
119 "Macro for defining a component class.
120
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.
124
125 NAME, SUPERS and SLOTS as treated as per defclass. The following
126 extra options are allowed:
127
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.
133
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.
138
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.
144
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.")
149
150 ;;; Extra Slot Options
151 "Other than the initargs for standard slots the following
152 options can be passed to component slots:
153
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.
158
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
167 object."
168 </src>
169
170 *** Windows
171
172 A window-component represents a top level browser window, naturally.
173
174 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/window.lisp][src/components/window.lisp]]
175
176 <src lang="lisp">
177 (defclass window-component ()
178 ((content-type)))
179
180 (defclass simple-window-component (window-component)
181 ((title)
182 (stylesheet)
183 (javascript :documentation "List of javascript includes.
184
185 Each element must be a list whose first value is either the
186 symbol :SRC or :JS.
187
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>.
191
192 The elements will be rendered in order.")
193 ...))
194 </src>
195
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
198 type.
199
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.
205
206 **** Status Bar
207
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]]
209
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.
214
215 <src lang="lisp">
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.")
221 ...)
222 (:documentation "Stateless status bar to display messages."))
223
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."))
227 </src>
228
229 <src lang="lisp">
230 (defcomponent status-bar-mixin ()
231 ((status-bar :accessor status-bar
232 :initarg status-bar
233 :component (status-bar))))
234
235 (defmethod show-status-bar ((win status-bar-mixin))
236 (render (status-bar win)))
237
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"))
241 </src>
242
243 **** Redirect
244
245 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/redirect.lisp][src/components/redirect.lisp]]
246
247 <src lang="lisp">
248 (defclass redirect-component ()
249 ((target :accessor target :initarg :target))
250 (:metaclass standard-component-class)
251 (:documentation "Send a client redirect.
252
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.
257
258 The redirect component never answers."))
259 </src>
260
261 There is also a =meta-refresh= procedure.
262
263 <src lang="lisp">
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
268 heart's content.")
269 </src>
270
271 *** Containers
272
273 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/container.lisp][src/components/container.lisp]]
274
275 <src lang="lisp">
276 (defclass container ()
277 (...)
278 (:metaclass standard-component-class)
279 (:documentation "Allow multiple components to share the same place.
280
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
284 list-container).
285
286 Each contained component has a \"key\" associated with it which
287 is used to retrieve a particular component. Keys are compared with
288 container.key-test.
289
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."))
293 </src>
294
295 **** Protocol
296
297 - =child-components=
298 - =find-component CONTAINER KEY=
299 - =remove-component=
300 - =(setf find-component CONTAINER KEY) COMPONENT= ->
301 =add-component CONTAINER COMPONENT KEY=
302
303 **** Switching Container
304
305 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/container.lisp][src/components/container.lisp]]
306
307 <src lang="lisp">
308 (defclass switching-container ...
309 (:documentation "A simple renderable container component.
310
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.
314
315 The switching-container component class is generally used as the super
316 class for navigatation components and tabbed-pane like
317 components."))
318 </src>
319
320 Subclass and =(defmethod render :around ...)= to render navigation using
321 =(call-next-method)= to render the selected component.
322
323 ***** Protocol
324
325 - =container.current-component COMPONENT=
326 - =(setf container.current-component CONTAINER) COMPONENT=
327
328 **** Tabbed Pane
329
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]]
331
332 <src lang="lisp">
333 (defcomponent tabbed-pane (switching-container)
334 (:documentation "Component for providing the user with a standard \"tabbed pane\" GUI widget."))
335 </src>
336
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.
340
341 *** Dialogs
342
343 A few convenience dialogs are provided for grabbing data from the
344 user.
345
346 **** login
347
348 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/login.lisp][src/components/login.lisp]]
349
350 <src lang="lisp">
351 (defclass login ()
352 ((username) (password) (message))
353 (:documentation "Generic login (input username and password) component.
354
355 This component, which must be embedded in another component,
356 presents the user with a simple two fielded login form.
357
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\"
363 message).
364
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))
370 </src>
371
372 <src lang="lisp">
373 (defgeneric check-credentials (login)
374 (:documentation "Returns T if LOGIN is valid."))
375
376 (defaction login-successful ((l login))
377 (answer t))
378 </src>
379
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]]
381
382 <src lang="lisp">
383 (defcomponent user-login (simple-window-component status-bar-mixin)
384 ((username string-field) (password password-field)))
385 </src>
386
387 Used by =secure-application-module= to provide a user login. Relevant
388 protocol details follow.
389
390 <src lang="lisp">
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))
396 user)))
397
398 (defgeneric application-find-user (application username)
399 (:documentation "Find USER by USERNAME for APPLICATION."))
400 </src>
401
402 **** error
403
404 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/error.lisp][src/components/error.lisp]]
405
406 <src lang="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
410 error messages.")
411 (:metaclass standard-component-class))
412
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))
420 </src>
421
422 **** message
423
424 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/message.lisp][src/components/message.lisp]]
425
426 <src lang="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.
431
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
435 never answer.")
436 (:metaclass standard-component-class))
437 </src>
438
439 **** option-dialog
440
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]]
442
443 <src lang="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.
448
449 The value of the slot MESSAGE is used as a general heading.
450
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,
453 will answer VALUE.
454
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
457 submit that value.")
458 (:metaclass standard-component-class))
459 </src>
460
461 A macro to present an option dialog is provided.
462
463 <src lang="lisp">
464 (defmacro option-dialog ((message-spec &rest message-args) &body options)
465 ...)
466 </src>
467
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.
471
472 *** Forms
473
474 Reasonably useful forms library that integrates easily with TAL.
475
476 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/form.lisp][src/components/form.lisp]]
477
478 <src lang="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
484 is rendered.")))
485
486 (defgeneric value (form-field)
487 (:documentation "The lispish translated value that represents the form-field."))
488
489 (defgeneric (setf value) (new-value form-field)
490 (:documentation "Set the value of a form-field with translation to client."))
491
492 (defclass generic-html-input (form-field html-element)
493 ((client-value :accessor client-value :initarg :client-value
494 :initform ""
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_")))
501 </src>
502
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.
507
508 <src lang="lisp">
509 (deftag-macro <ucw:input (&attribute accessor action reader writer name id (default nil)
510 &allow-other-attributes others)
511 "Generic INPUT tag replacement.
512
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.
516
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
522 the latest wins.")
523 </src>
524
525 Validation of form fields are supported by adding to the validators
526 list.
527
528 <src lang="lisp">
529 (defclass validator ()
530 ((message :accessor message :initarg :message :initform nil)))
531
532 (defgeneric validate (field validator)
533 (:documentation "Validate a form-field with a validator."))
534
535 (defgeneric javascript-check (field validator)
536 (:documentation "Generate javascript code for checking FIELD against VALIDATOR.
537
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."))
542
543 (defgeneric javascript-invalid-handler (field validator)
544 (:documentation "The javascript code body for when a field is invalid."))
545
546 (defgeneric javascript-valid-handler (field validator)
547 (:documentation "Generate the javascript body for when a field is valid."))
548 </src>
549
550 **** Standard Form Fields
551
552 <src lang="lisp">
553 (defclass string-field (generic-html-input)
554 ((input-size) (maxlength)))
555
556 (defclass password-field (string-field))
557 (defclass number-field (string-field))
558 (defclass integer-field (number-field))
559
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
567 argument.")))
568
569 (defclass textarea-field (generic-html-input)
570 ((rows) (cols)))
571
572 (defclass date-field (form-field widget-component)
573 ((year) (month) (day)))
574
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))
578
579 (defclass select-field (generic-html-input)
580 ((data-set :documentation "The values this select chooses
581 from."))
582 (:documentation "Form field used for selecting one value from a
583 list of available options."))
584
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
588 <ucw:option tag."))
589
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
594 method."))
595
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))
599
600 (defclass radio-group (generic-html-input)
601 ((value-widgets)))
602
603 (defclass radio-button (generic-html-input)
604 ((value)
605 (group :documentation "The RADIO-GROUP this button is a part
606 of."))
607 (:documentation "A widget representing a single radio
608 button. Should be used in conjunction with a RADIO-GROUP."))
609
610 (defmethod add-value ((group radio-group) value)
611 "Adds radio-button with value to group")
612
613 (defclass checkbox-field (generic-html-input))
614 (defclass file-upload-field (generic-html-input))
615 (defclass submit-button (generic-html-input)
616 ((label)))
617 </src>
618
619 ***** File Upload Field
620
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.
625
626 <src lang="lisp">
627 (defgeneric mime-part-headers (mime-part)
628 (:documentation "Returns an alist of the headers of MIME-PART.
629
630 The alist must be of the form (NAME . VALUE) where both NAME and
631 VALUE are strings."))
632
633 (defgeneric mime-part-body (mime-part)
634 (:documentation "Returns the body of MIME-PART."))
635 </src>
636
637 **** Standard Validators
638
639 <src lang="lisp">
640 (defclass not-empty-validator (validator))
641
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."))
645
646 (defclass length-validator (value-validator)
647 ((min-length :accessor min-length :initarg :min-length
648 :initform nil)
649 (max-length :accessor max-length :initarg :max-length
650 :initform nil)))
651
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."))
655
656 (defclass regex-validator (value-validator)
657 ((regex :accessor regex :initarg :regex :initform nil)))
658
659 (defclass e-mail-address-validator (regex-validator))
660
661 (defclass phone-number-validator (regex-validator))
662
663 (defclass is-a-number-validator (value-validator))
664 (defclass is-an-integer-validator (is-a-number-validator))
665
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)))
669 </src>
670
671 **** Simple Form Helper
672
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=.
676
677 <src lang="lisp">
678 (defcomponent simple-form (html-element)
679 ((submit-method :accessor submit-method
680 :initform "post"
681 :initarg :submit-method)
682 (dom-id :accessor dom-id
683 :initform (js:gen-js-name-string :prefix "_ucw_simple_form_")
684 :initarg :dom-id))
685 (:default-initargs :dom-id "ucw-simple-form"))
686 </src>
687
688 *** Templates
689
690 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/template.lisp][src/components/template.lisp]]
691
692 Infrastructure for loading TAL templates as a view of a component.
693
694 <src lang="lisp">
695 (defclass template-component (component))
696 (defcomponent simple-template-component (template-component)
697 ((environment :initarg :environment :initform nil)))
698
699 (defgeneric template-component-environment (component)
700 (:documentation "Create the TAL environment for rendering COMPONENT's template.
701
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
705 environments.)")
706 (:method-combination nconc))
707
708 (defmethod template-component-environment nconc ((component template-component))
709 "Create the basic TAL environment.
710
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,
714 visable."
715 (make-standard-environment `((component . ,component)) component))
716
717 (defmethod render ((component template-component))
718 "Render a template based component.
719
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)))
728
729 </src>
730
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.
734
735 *** Utility Mixin Components
736
737 **** Range View
738
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]]
740
741 <src lang="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.
745
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.
750
751 The generic function RENDER-RANGE-VIEW-ITEM is used to render
752 each item of DATA.
753
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))
758 </src>
759
760 <src lang="lisp">
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))
767 (<:as-html item)))
768 </src>
769
770 **** Widget
771
772 Mixin with existing component to wrap in a div or span. This is handy
773 for defining lightweight widgets embedded within other components.
774
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]]
776
777 <src lang="lisp">
778 (defclass html-element (component)
779 ((css-class)
780 (dom-id)
781 (css-style)
782 (extra-tags)
783 (events))
784 (:documentation "An HTML element.
785
786 HTML elements control aspects that are relevant to almost all tags.
787
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
791 tag.
792
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
795
796 (event parenscript-statement*)
797
798 For example (\"onclick\" (alert \"You clicked!\") (return nil)). If
799 the element has a dom-id, these event handlers are automatically
800 added."))
801 </src>
802
803 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/widget.lisp][src/components/widget.lisp]]
804
805 <src lang="lisp">
806 (defclass widget-component (html-element)
807 ()
808 (:documentation "A widget which should be wrapped in a <div>."))
809
810 (defclass inline-widget-component (html-element)
811 ()
812 (:documentation "A widget which should be wrapped in <span> and not <div>"))
813
814 (defmethod render :wrap-around ((widget widget-component)))
815 (defmethod render :wrap-around ((widget inline-widget-component)))
816 </src>
817
818 **** Transactions
819
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,
826 naturally.
827
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]]
829
830 <src lang="lisp">
831 (defcomponent transaction-mixin ()
832 (...))
833
834 (defmethod/cc open-transaction ((comp transaction-mixin)))
835 (defmethod/cc close-transaction ((comp transaction-mixin)))
836 </src>
837
838 **** Task
839
840 =(defaction start ...)= on subclass to run a series of actions bundled
841 into a task.
842
843 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/task.lisp][src/components/task.lisp]]
844
845 <src lang="lisp">
846 (defclass task-component (standard-component)
847 (...)
848 (:documentation "A controller for a single task or operation to
849 be performed by the user.
850
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."))
855
856 (defgeneric/cc start (task)
857 (:documentation "action which gets called automatically when
858 task-component is active. Use defaction to define your own
859 \"start\" action"))
860 </src>
861
862 **** Cached
863
864 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/cached.lisp][src/components/cached.lisp]]
865
866 <src lang="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
873 method." )
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.
880
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.
884
885 Do not use CACHED-COMPONENT directly, use one its subclasses."))
886
887 (defgeneric component-dirty-p (component)
888 (:documentation "Returns T is COMPONENT's cache is invalid."))
889
890 (defgeneric update-cache (component)
891 (:documentation "Update COMPONENT's cache variables after a refresh."))
892
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."))
897 (:default-initargs
898 :timeout (* 30 60 60))
899 (:documentation "Render the component at most every TIMEOUT seconds."))
900
901 (defcomponent num-hits-cache-component (cached-component)
902 ((hits-since-refresh :accessor hits-since-refresh
903 :initform nil
904 :documentation "Number of views since last refresh."))
905 (:default-initargs :timeout 10)
906 (:documentation "Render the component every TIMEOUT views."))
907 </src>
908
909 Subclass and override =component-dirty-p= to do something useful
910 (e.g. flip mark bit when object being presented changes).
911
912 ** Control Flow
913
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]]
915
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]]
917
918 *** Calling
919
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.
924
925 <src lang="lisp">
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.
929
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.
934
935 Notes:
936
937 This macro assumes that the lexcial variable UCW:SELF is bound to
938 the calling component.")
939
940 (answer VAL) ; answer parent component ONLY IN ACTIONS
941
942 (ok SELF VAL) ; Used to answer a component anywhere and what answer
943 ; expands into
944
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)
949 </src>
950
951 =(call COMPONENT-NAME &ARGS INIT-ARGS)= calls =COMPONENT-NAME= and returns
952 the value returned by =(ok SELF RETURN-VALUE)= called from within
953 =COMPONENT-NAME=
954
955 *** Actions
956
957 Actions are methods on components. The first argument **must** be a
958 component for most of UCW to work.
959
960 <src lang="lisp">
961 (defaction NAME (first ...) ...)
962 ; (roughly) expands into
963 (defmethod/cc NAME (first ...)
964 (let ((self first))
965 ...))
966 </src>
967
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
972 macroexpansion!").
973
974 *** Entry Points
975
976 <src lang="lisp">
977 (defentry-point url (:application APPLICATION
978 :class DISPATCHER-CLASS)
979 (PARAM1 ... PARAMN) ; GET / POST vars, bound in body
980 body)
981 </src>
982
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.
988
989 <src lang="lisp">
990 (defentry-point "^(audio.ucw|)$" (:application *golf-test-app*
991 :class regexp-dispatcher)
992 (file)
993 (call 'audio-file-window
994 :audio-file (make-instance 'audio-file
995 :type :vorbis
996 :data (file->bytes (open
997 file
998 :element-type 'unsigned-byte)))))
999 </src>
1000
1001 ** Dispatching
1002
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]]
1004
1005 <src lang="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.
1010
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*."))
1016
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
1020 matcher.
1021
1022 These methods may modify context as they wish since they'r
1023 matched, request will be closed after this method is run."))
1024
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.
1028
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*."))
1034 </src>
1035
1036 <src lang="lisp">
1037 (defclass my-matcher (abstract-matcher) ...)
1038 (defclass my-handler (abstract-handler) ...)
1039 (defclass my-dispatcher (abstract-dispatcher my-matcher my-handler)
1040 ...)
1041 </src>
1042
1043 *** Simple Dispatcher
1044
1045 <src lang="lisp">
1046 (:documentation "This class of dispatchers avoids all of UCW's
1047 standard call/cc (and therefore frame/backtracking/component)
1048 mechanism.
1049
1050 Unlike all other UCW dispatchers a simple-dispatcher must not use
1051 CALL, and must perform the rendering directly within the handler.")
1052 </src>
1053
1054 ** Server
1055
1056 [[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/control.lisp][src/control.lisp]]
1057
1058 <src lang="lisp">
1059 (defun create-server (&key
1060 (backend `(,*ucw-backend-type* :host ,*ucw-backend-host*
1061 :port ,*ucw-backend-port*))
1062 (applications *ucw-applications*)
1063 (start-p t)
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*.
1069
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
1076 MAKE-BACKEND.
1077
1078 APPLICATIONS is a list of defined applications to be loaded into the
1079 server.
1080
1081 Logs are generated in verbosity defined by LOG-LEVEL and directed to
1082 LOG-ROOT-DIRECTORY if defined."
1083 ...
1084 server) ; return server, naturally
1085 </src>
1086
1087 ** Debugging
1088
1089 *** Inspector
1090
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]]
1092
1093 <src lang="lisp">
1094 (defaction call-inspector ((component component) datum)
1095 "Call an inspector for DATUM on the component COMPONENT."
1096 (call 'ucw-inspector :datum datum))
1097 </src>
1098
1099 * Tips
1100
1101 ** Getting dojo to load
1102
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).
1109
1110 <src lang="lisp">
1111 :www-roots (list '("dojo/" .
1112 #P"/home/clinton/src/ucw/darcs/ucw_dev/wwwroot/dojo/"))
1113 </src>
1114
1115 ** Specials Bound During Rendering
1116
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
1119 =render=.
1120
1121 ** Printing to the yaclml stream
1122
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.