Revert "html update"
[clinton/website/site/unknownlamer.org.git] / UCWNotes.muse
CommitLineData
8a7c1bf7 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
6Lisp, but is a bit difficult to learn. It is documented
7extensively--in the form of docstrings. These are extremely helpful
8once you've figured out the rough structure of UCW, but they are of no
9help when first learning unless you just read most of the source. I
10ended 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
12public consumption.
13
14The roadmap is presented with major sections ordered in a logical
15order for learning the framework. The sections are ordered internally
16in order of most immediately useful to least, but it may be worth
17hopping between major sections before reading all of the details. I
18have used abridged class definitions and docstrings with occasional
19commentary to clarify things.
20
21* Roadmap
22
23** Applications
24
25Applications are a bundle of entry points. The base class is,
26naturally, =standard-application=, but you should instead derive your
27application class from =modular-application= and any standard or custom
28application 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
36start (prefix) of all the urls this app should handle.
37
38This value is used by the standard-server to decide what app a
39particular request is aimed at and for generating links to
40actions within the app. ")
41 (www-roots :initarg :www-roots
42 :documentation "A list of directories (pathname
43specifiers) or cons-cell (URL-subdir . pathname) to use when looking for static files.")
44 (dispatchers :initarg :dispatchers
45 :documentation "A list of request
46dispatchers. The user supplied list of dispatchers is extended
47with other dispatchers that are required for UCW to function
48properly (action-dispatcher, a parenscript-dispatcher, etc). If
49you want full control over the active dispatchers use the (setf
50application.dispatchers) accessor or, if you want control over
51the 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
75Cookie session applications work exactly like
76standard-applications except that when the session is not found
77using the standard mechanisms the id is looked for in a cookie."))
78</src>
79
80This is the most useful of the application components. It makes your
81application urls readable by stashing the session id into a cookie
82rather 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.
101Concrete application must specialize the following methods:
102APPLICATION-FIND-USER (APPLICATION USERNAME)
103APPLICATION-CHECK-PASSWORD (APPLICATION USER PASSWORD)
104APPLICATION-AUTHORIZE-CALLZE-CALL (APPLICATION USER FROM-COMPONENT TO-COMPONENT)."))
105</src>
106
107** Components
108
109A component is a special class that handles the complexities of
110continuation suspension and such for you. New components are derived
111from the existing ones by using =defcomponent= instead of =defclass=. This
112adds a few extra slot and class options, and ensures that the proper
113metaclass 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
121This macro is used to create component classes and provides
122options for easily creating the methods which often accompany a
123component definition.
124
125NAME, SUPERS and SLOTS as treated as per defclass. The following
126extra 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
152options can be passed to component slots:
153
154:backtrack [ T | NIL | FUNCTION-NAME ] - Specify that this slot
155should be backtracked (or not if NIL is passed as the value). If
156the value is neither T nor NIL then it must be a function which
157will be used as the copyer.
158
159:component [ TYPE | ( TYPE &rest INITARGS ) ] - Specify that this
160slot is actually a nested component of type TYPE. When instances
161of the class are created this slot will be set to an instance of
162type TYPE and it's place will be set to this slot. If a list is
163passed to :component then TYPE (which isn't evaluated) will be
164passed as the first argument to make-instance. The INITARGS will
165be eval'd and apply'd to make-instance. The result of this call
166to make-instance will be used as the effective component
167object."
168</src>
169
170*** Windows
171
172A 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
185Each element must be a list whose first value is either the
186symbol :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
192The elements will be rendered in order.")
193 ...))
194</src>
195
196=window-component= could be useful for doing things like dumping binary
197data to the user, or just deriving your own funky top level window
198type.
199
200=simple-window-component= is the easiest for displaying standard
201webpage. It provides a wrapping method on render that displays the
202html boilerplate based on your component slot values which is what one
203wants most of the time. The initargs to =simple-window-component= have
204the 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
210There is a generic status bar interface. Messages severity is one of
211=(:error :warn :info)=. Note that the default status bar render method
212just shows a div with status messages. A derivative could be defined
213to 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
218show. Each element is a cons of the form (SEVERITY .
219MESSAGE). SEVERITY is one of :ERROR, :WARN, :INFO and MESSAGE is
220a 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
226severity 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
253This component, which must be used as a window-component,
254redirects the client to the url specified in the target slot. A
255302 (as opposed to 303) response code is sent to ensure
256compatability with older browsers.
257
258The redirect component never answers."))
259</src>
260
261There 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.
266This is useful in order to have a GET url after a form POST's
267actions have completed running. The user can then refresh to his
268heart'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
281The container component serves to manage a set of components.
282It does not provide any render impementation, which is the
283resposibility of the subclasses (e.g. switching-container or
284list-container).
285
286Each contained component has a \"key\" associated with it which
287is used to retrieve a particular component. Keys are compared with
288container.key-test.
289
290The :contents inintarg, if provided, must be either a list of (key .
291component) or a list of components. In the latter case it will
292be 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
311This component is like the regular CONTAINER but serves to manage a set
312of components which share the same place in the UI. Therefore it provides
313an implementation of RENDER which simply renders its current component.
314
315The switching-container component class is generally used as the super
316class for navigatation components and tabbed-pane like
317components."))
318</src>
319
320Subclass 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
337Provides a generic tabbed pane that renders a nested div split into a
338naviation and content box. The navigation box is a set of styled divs
339containing the navigation links.
340
341*** Dialogs
342
343A few convenience dialogs are provided for grabbing data from the
344user.
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
355This component, which must be embedded in another component,
356presents the user with a simple two fielded login form.
357
358When the user attempts a login the action try-login is called,
359try-login calls the generic function check-credentials passing it
360the login component. If check-credentials returns true then the
361login-successful action is called, otherwise the message slot of
362the login component is set (to a generic \"bad username\"
363message).
364
365The default implementaion of login-successful simply answers t,
366no default implementation of check-credentials is
367provided. Developers should use sub-classes of login for which
368all 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
387Used by =secure-application-module= to provide a user login. Relevant
388protocol 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
432If the OK-TEXT slot is non-NIL component will use that as the
433text for a link which, when clicked, causes the component to
434answer. It follows that if OK-TEXT is NIL this component will
435never 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
449The value of the slot MESSAGE is used as a general heading.
450
451The OPTIONS slot must be an alist of (VALUE . LABEL). LABEL (a
452string) will be used as the text of a link which, when clikced,
453will answer VALUE.
454
455If the CONFIRM slot is T the user will be presented with a second
456OPTION-DIALOG asking the user if they are sure they want to
457submit that value.")
458 (:metaclass standard-component-class))
459</src>
460
461A 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
469used as a string literal otherwise. This does not provide a way to set
470the confirm property which makes the macro not so generally useful.
471
472*** Forms
473
474Reasonably 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
503Fields are rendered into the extended =<ucw:input= yaclml tag which
504supports a few fancy features. The =:accessor= for all form elements is
505set to =(client-value FIELD)=, and you should use =value= to access the
506Lisp 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
513If the ACCESSOR attribute is specified then it must be a PLACE
514and it's value will be used to fill the input, when the form is
515submitted it will be set to the new value.
516
517If ACTION is specefied then when the form is submitted via this
518input type=\"submit\" tag the form will be eval'd. when the
519submit (or image) is clicked. DEFAULT means that the ACTION
520provided for this input tag will be the default action of the
521form when pressing enter in a form field. If more then one, then
522the latest wins.")
523</src>
524
525Validation of form fields are supported by adding to the validators
526list.
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
538This is the convenience entry point to generate-javascript-check,
539methods defined on this generic funcition should return a list of
540javascript code (as per parenscript) which tests against the
541javascript 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
621Calling =value= on a =file-upload-field= returns a mime encoded body
622part. =(mime-part-body (value FIELD))= will return a **binary stream**
623attached to the contents of the file. The =Content-Type= header should
624be 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
630The alist must be of the form (NAME . VALUE) where both NAME and
631VALUE 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.
644That 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
673UCW provides a helper class for developing forms. Subclass and add the
674elements you wish to include in the form. A =:wrapping= method renders
675the 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
692Infrastructure 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
702Methods defined on this generic function must return a TAL
703environment: a list of TAL binding sets (see the documentation
704for YACLML:MAKE-STANDARD-ENVIRONMENT for details on TAL
705environments.)")
706 (:method-combination nconc))
707
708(defmethod template-component-environment nconc ((component template-component))
709 "Create the basic TAL environment.
710
711Binds the symbol ucw:component to the component object itself,
712also puts the object COMPONENT on the environment (after the
713binding of ucw:component) so that slots are, by default,
714visable."
715 (make-standard-environment `((component . ,component)) component))
716
717(defmethod render ((component template-component))
718 "Render a template based component.
719
720Calls the component's template. The name of the template is the
721value returned by the generic function
722template-component.template-name, the template will be rendered
723in the environment returned by the generic function
724template-component-environment."
725 (render-template *context*
726 (template-component.template-name component)
727 (template-component-environment component)))
728
729</src>
730
731Subclass and override methods. =simple-template-component= only provides
732the ability to set environment variables in initarg. Subclass to
733provide 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
746The data set is presented one \"window\" at a time with links to
747the the first, previous, next and last window. Each window shows
748at most WINDOW-SIZE elements of the data. The data is passed to
749the range-view at instance creation time via the :DATA initarg.
750
751The generic function RENDER-RANGE-VIEW-ITEM is used to render
752each item of DATA.
753
754In order to change the rendering of the single elements of a
755range view developer's should create a sub class of RANGE-VIEW
756and 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
765applies ITEM to princ (via <:as-html)."
766 (declare (ignore range-view))
767 (<:as-html item)))
768</src>
769
770**** Widget
771
772Mixin with existing component to wrap in a div or span. This is handy
773for 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
786HTML elements control aspects that are relevant to almost all tags.
787
788Firstly they provide a place to store the class, id, and style of the
789component. The specific render methods of the components themselves
790must pass these values to whatever code is used to render the actual
791tag.
792
793Secondly, they allow javascript event handlers to be registered for a
794tag. The events slot can be filled with a list of lists in the form
795
796 (event parenscript-statement*)
797
798For example (\"onclick\" (alert \"You clicked!\") (return nil)). If
799the element has a dom-id, these event handlers are automatically
800added."))
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
820A mixin to provide transactions. =(open-transaction component)= and
821=(close-transaction component)= open and closed nested
822transactions. After a transaction has been closed an attempt to
823backtrack into a step inside the transaction will result in jumping up
824one level of transactions (or out of the transaction entirely if at
825the top level). This ensures that the transaction is only run once,
826naturally.
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
841into 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
852component is instantiated. Task components do not have their own
853RENDER method, in fact they have no graphical representation but
854serve only to order a sequence of other components."))
855
856(defgeneric/cc start (task)
857 (:documentation "action which gets called automatically when
858task-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
881The component caching API is built around the generic functions
882COMPONENT-DIRTY-P and REFRESH-COMPONENT-OUTPUT and a method on
883RENDER, see the respective docstrings for more details.
884
885Do 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
909Subclass 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
920Most of what you do in UCW will be calling components so this is a bit
921important. Note that calling interrupts the current control flow so if
922you want to render a component in place as part of another component
923just 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
928a freshly created component of type COMPONENT-TYPE.
929
930COMPONENT-INIT-ARGS are passed directly to the underlying
931make-instance call. This form will return if and when the call'd
932component calls answer, the value returned by this form is
933whatever the call'd component passed to answer.
934
935Notes:
936
937This macro assumes that the lexcial variable UCW:SELF is bound to
938the 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
952the value returned by =(ok SELF RETURN-VALUE)= called from within
953=COMPONENT-NAME=
954
955*** Actions
956
957Actions are methods on components. The first argument **must** be a
958component 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
969most UCW control flow things to work. =defaction= hides this from you,
970and was a big source of confusion for me early on (mostly "hmm, why is
971this not working ... where did that come from in the
972macroexpansion!").
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
983An entry point is what it sounds like: a static URL matched using the
984mater of =DISPATCHER-CLASS= that enters into =APPLICATION= running the
985code in =body=. An example from a test program I have written
986follows. The entry point allows files to be streamed to user when the
987url 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
1008matcher. This method would return multiple-values according to
1009matcher internal nature.
1010
1011No methods defined on this function may rebind *context*, nor
1012change CONTEXT's application. Only if the method matches the
1013request, it is allowed to modify CONTEXT or APPLICATION, even in
1014that case methods defined on this function must not modify
1015CONTEXT's application nor rebind *context*."))
1016
1017(defgeneric handler-handle (handler application context matcher-result)
1018 (:documentation "Abstract function for handler classes to
1019implement in order to handle a request matched by relevant
1020matcher.
1021
1022These methods may modify context as they wish since they'r
1023matched, 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
1029No methods defined on this function may rebind *context*, nor
1030change CONTEXT's application. Only if the method returns T is it
1031allowed to modify CONTEXT or APPLICATION, even in that case
1032methods defined on this function must not modify CONTEXT's
1033application 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
1050Unlike all other UCW dispatchers a simple-dispatcher must not use
1051CALL, 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
1068PORT. Affects *DEFAULT-SERVER*.
1069
1070BACKEND is a list of (BACKEND-TYPE &rest INITARGS). BACKEND-TYPE
1071may be :HTTPD, :MOD-LISP, :ASERVE, :ARANEIDA, an existing
1072backend, an existing UCW server backend or :DEFAULT in which case
1073it attempts to return a sane default from the UCW backends loaded
1074and available, or any other value for which a valid MAKE-BACKEND
1075method has been defined. INITARGS will be passed, unmodified, to
1076MAKE-BACKEND.
1077
1078APPLICATIONS is a list of defined applications to be loaded into the
1079server.
1080
1081Logs are generated in verbosity defined by LOG-LEVEL and directed to
1082LOG-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
1103I had some trouble getting dojo to work properly with UCW. The way
1104that the =:www-roots= option for an application works is a bit
1105confusing, and it is unforgiving if you mess the pathname up. A
1106directory **must** have a =/= at the end, and the directory you are serving
1107must also have the =/= (which is counterintuitive given the behavior of
1108most 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
1117The current request context is bound to =ucw:*context*=, and the current
1118component is bound to =ucw:*current-component*= in the dynamic extent of
1119=render=.
1120
1121** Printing to the yaclml stream
1122
1123Occasionally it can be useful to do something like write a byte array
1124as 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
1126you wish to have content interspersed with yaclml tags.