."))
(defclass inline-widget-component (html-element)
()
(:documentation "A widget which should be wrapped in
and not "))
(defmethod render :wrap-around ((widget widget-component)))
(defmethod render :wrap-around ((widget inline-widget-component)))
**** Transactions
A mixin to provide transactions. =(open-transaction component)= and
=(close-transaction component)= open and closed nested
transactions. After a transaction has been closed an attempt to
backtrack into a step inside the transaction will result in jumping up
one level of transactions (or out of the transaction entirely if at
the top level). This ensures that the transaction is only run once,
naturally.
[[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/transaction-mixin.lisp][src/components/transaction-mixin.lisp]]
(defcomponent transaction-mixin ()
(...))
(defmethod/cc open-transaction ((comp transaction-mixin)))
(defmethod/cc close-transaction ((comp transaction-mixin)))
**** Task
=(defaction start ...)= on subclass to run a series of actions bundled
into a task.
[[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/task.lisp][src/components/task.lisp]]
(defclass task-component (standard-component)
(...)
(:documentation "A controller for a single task or operation to
be performed by the user.
A task component's START action is called as soon as the
component is instantiated. Task components do not have their own
RENDER method, in fact they have no graphical representation but
serve only to order a sequence of other components."))
(defgeneric/cc start (task)
(:documentation "action which gets called automatically when
task-component is active. Use defaction to define your own
\"start\" action"))
**** Cached
[[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/components/cached.lisp][src/components/cached.lisp]]
(defcomponent cached-component ()
((cached-output :accessor cached-output :initform nil
:documentation "A string holding the output to
use for this component. This string will be
written directly to the html stream and is
changed by the REFRESH-COMPONENT-OUTPUT
method." )
(timeout :accessor timeout :initarg :timeout
:documentation "An value specifying how often this
component needs to be refreshed. The exact
interpretation of the value depends on the type of
caching used class."))
(:documentation "Component which caches its output.
The component caching API is built around the generic functions
COMPONENT-DIRTY-P and REFRESH-COMPONENT-OUTPUT and a method on
RENDER, see the respective docstrings for more details.
Do not use CACHED-COMPONENT directly, use one its subclasses."))
(defgeneric component-dirty-p (component)
(:documentation "Returns T is COMPONENT's cache is invalid."))
(defgeneric update-cache (component)
(:documentation "Update COMPONENT's cache variables after a refresh."))
(defcomponent timeout-cache-component (cached-component)
((last-refresh :accessor last-refresh :initform nil
:documentation "The time, exrpessed as a
universal time, when the component was last rendered."))
(:default-initargs
:timeout (* 30 60 60))
(:documentation "Render the component at most every TIMEOUT seconds."))
(defcomponent num-hits-cache-component (cached-component)
((hits-since-refresh :accessor hits-since-refresh
:initform nil
:documentation "Number of views since last refresh."))
(:default-initargs :timeout 10)
(:documentation "Render the component every TIMEOUT views."))
Subclass and override =component-dirty-p= to do something useful
(e.g. flip mark bit when object being presented changes).
** Control Flow
[[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-component/control-flow.lisp][src/rerl/standard-component/control-flow.lisp]]
[[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-action.lisp][src/rerl/standard-action.lisp]]
*** Calling
Most of what you do in UCW will be calling components so this is a bit
important. Note that calling interrupts the current control flow so if
you want to render a component in place as part of another component
just call =render= on it instead.
(defmacro call (component-type &rest component-init-args)
"Stop the execution of the current action and pass control to
a freshly created component of type COMPONENT-TYPE.
COMPONENT-INIT-ARGS are passed directly to the underlying
make-instance call. This form will return if and when the call'd
component calls answer, the value returned by this form is
whatever the call'd component passed to answer.
Notes:
This macro assumes that the lexcial variable UCW:SELF is bound to
the calling component.")
(answer VAL) ; answer parent component ONLY IN ACTIONS
(ok SELF VAL) ; Used to answer a component anywhere and what answer
; expands into
(jump COMPONENT-NAME &REST ARGS) ; is similar to call, but replaces
; the current component with the new
; one and drops any backtracks (back
; button will no longer work)
=(call COMPONENT-NAME &ARGS INIT-ARGS)= calls =COMPONENT-NAME= and returns
the value returned by =(ok SELF RETURN-VALUE)= called from within
=COMPONENT-NAME=
*** Actions
Actions are methods on components. The first argument **must** be a
component for most of UCW to work.
(defaction NAME (first ...) ...)
; (roughly) expands into
(defmethod/cc NAME (first ...)
(let ((self first))
...))
=Self= being bound in the current lexical environment is required for
most UCW control flow things to work. =defaction= hides this from you,
and was a big source of confusion for me early on (mostly "hmm, why is
this not working ... where did that come from in the
macroexpansion!").
*** Entry Points
(defentry-point url (:application APPLICATION
:class DISPATCHER-CLASS)
(PARAM1 ... PARAMN) ; GET / POST vars, bound in body
body)
An entry point is what it sounds like: a static URL matched using the
mater of =DISPATCHER-CLASS= that enters into =APPLICATION= running the
code in =body=. An example from a test program I have written
follows. The entry point allows files to be streamed to user when the
url audio.ucw?file=FOO is used.
(defentry-point "^(audio.ucw|)$" (:application *golf-test-app*
:class regexp-dispatcher)
(file)
(call 'audio-file-window
:audio-file (make-instance 'audio-file
:type :vorbis
:data (file->bytes (open
file
:element-type 'unsigned-byte)))))
** Dispatching
[[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/rerl/standard-dispatcher.lisp][src/rerl/standard-dispatcher.lisp]]
(defgeneric matcher-match (matcher application context)
(:documentation "Abstract method for subclasses to implement a
matcher. This method would return multiple-values according to
matcher internal nature.
No methods defined on this function may rebind *context*, nor
change CONTEXT's application. Only if the method matches the
request, it is allowed to modify CONTEXT or APPLICATION, even in
that case methods defined on this function must not modify
CONTEXT's application nor rebind *context*."))
(defgeneric handler-handle (handler application context matcher-result)
(:documentation "Abstract function for handler classes to
implement in order to handle a request matched by relevant
matcher.
These methods may modify context as they wish since they'r
matched, request will be closed after this method is run."))
(defgeneric dispatch (dispatcher application context)
(:documentation "Entry point into a dispatcher. Must return T
if the context has been handled or NIL if it hasn't.
No methods defined on this function may rebind *context*, nor
change CONTEXT's application. Only if the method returns T is it
allowed to modify CONTEXT or APPLICATION, even in that case
methods defined on this function must not modify CONTEXT's
application nor rebind *context*."))
(defclass my-matcher (abstract-matcher) ...)
(defclass my-handler (abstract-handler) ...)
(defclass my-dispatcher (abstract-dispatcher my-matcher my-handler)
...)
*** Simple Dispatcher
(:documentation "This class of dispatchers avoids all of UCW's
standard call/cc (and therefore frame/backtracking/component)
mechanism.
Unlike all other UCW dispatchers a simple-dispatcher must not use
CALL, and must perform the rendering directly within the handler.")
** Server
[[http://www.uncommon-web.com/darcsweb/darcsweb.cgi?r=ucw_dev;a=headblob;f=/src/control.lisp][src/control.lisp]]
(defun create-server (&key
(backend `(,*ucw-backend-type* :host ,*ucw-backend-host*
:port ,*ucw-backend-port*))
(applications *ucw-applications*)
(start-p t)
(server-class *ucw-server-class*)
(log-root-directory (truename *ucw-log-root-directory*))
(log-level *ucw-log-level*))
"Creates and returns a UCW server according to SERVER-CLASS, HOST and
PORT. Affects *DEFAULT-SERVER*.
BACKEND is a list of (BACKEND-TYPE &rest INITARGS). BACKEND-TYPE
may be :HTTPD, :MOD-LISP, :ASERVE, :ARANEIDA, an existing
backend, an existing UCW server backend or :DEFAULT in which case
it attempts to return a sane default from the UCW backends loaded
and available, or any other value for which a valid MAKE-BACKEND
method has been defined. INITARGS will be passed, unmodified, to
MAKE-BACKEND.
APPLICATIONS is a list of defined applications to be loaded into the
server.
Logs are generated in verbosity defined by LOG-LEVEL and directed to
LOG-ROOT-DIRECTORY if defined."
...
server) ; return server, naturally
** Debugging
*** Inspector
[[pos:///home/clinton/src/ucw/darcs/ucw_dev/src/components/ucw-inspector.lisp#399][/home/clinton/src/ucw/darcs/ucw_dev/src/components/ucw-inspector.lisp]]
(defaction call-inspector ((component component) datum)
"Call an inspector for DATUM on the component COMPONENT."
(call 'ucw-inspector :datum datum))
* Tips
** Getting dojo to load
I had some trouble getting dojo to work properly with UCW. The way
that the =:www-roots= option for an application works is a bit
confusing, and it is unforgiving if you mess the pathname up. A
directory **must** have a =/= at the end, and the directory you are serving
must also have the =/= (which is counterintuitive given the behavior of
most unix things that don't want the =/= at the end of the name).
:www-roots (list '("dojo/" .
#P"/home/clinton/src/ucw/darcs/ucw_dev/wwwroot/dojo/"))
** Specials Bound During Rendering
The current request context is bound to =ucw:*context*=, and the current
component is bound to =ucw:*current-component*= in the dynamic extent of
=render=.
** Printing to the yaclml stream
Occasionally it can be useful to do something like write a byte array
as an ascii string to the client. Inside of =render= the variable
=yaclml:*yaclml-stream*= is bound to the stream that you can write to if
you wish to have content interspersed with yaclml tags.