This is the user's guide to Weblocks.
http://teddyb.org/rlp/tiki-index.php?page!Learning+About+Weblocks
There is both excellent community and professional support available for Weblocks.
Community support is available via the Google group at http://groups.google.com/group/weblocks.
For commercial support and consulting contact Leslie P. Polzer <polzer@gnu.org>.
You can skip this section if you already have a comfortable Common Lisp environment.
First you must choose a Common Lisp implementation.
Weblocks is designed and implemented to be portable and should run on the most popular Common Lisp implementations. It is currently tested best on SBCL and Clozure CL, though.
There are at least two fundamentally different development approaches for using Common Lisp:
Example: Emacs/Slime, Vim/Limp, Lispworks/IDE.
Incremental development happens mainly on the S-Expression level. This means that you edit a SEXP and send it with the help of editor directly to your Lisp image, which evaluates it, thus affecting the current Lisp environment.
Example: Vim and your favorite terminal emulator. You start Vim in one window and your Lisp in another. Interaction happens by reloading your applications ASDF system and simple copy/paste of snippets.
We will try to be largely agnostic of the development approach in this manual which actually means that we tend towards the second approach: Lisp calls are referred to by what you'd type in your REPL, not by Emacs shortcut as it is often the case.
For a basic comfortable SBCL setup, see Appendix A.
There is a comprehensive installation guide on the main web site:
http://weblocks.viridian-project.de/installation
Weblocks comes with a simple application template generator.
Start your Lisp and run
This will initialize a barebones application called NAME in directory DIR.(wop:make-app 'NAME "DIR")
Another effective way to hack on a new application is copying the code of an application from the examples/ directory.
Widgets are the main building blocks of Weblocks (hence the name).
At the heart of Weblocks is a tree1 of widgets that is manipulated by the clients requests. When a
client sends its first request to a Weblocks application then a new session is
started for it and a widget tree is associated with this session.
This initial widget tree is computed as defined by the application developer. A special function (usually called init-user-session) is called by Weblocks to initialize a new session. This function then proceeds to set up a widget tree by adding children to the root of the widget tree (accessible with the macro root-widget).
The initial content and structure of the widget tree may depend on arbitrary factors; it is conceivable (although probably not very sensible) to generate different widget trees depending on the time of day.
A client's request for a specific URI modifies the widget tree: widgets called dispatchers choose their one child based on the current request URI.
There is a convenience function make-widget that creates widgets from strings (anything that can be printed, in fact) and functions.
Apart from session initialization the widget tree may be modified by actions.
Actions are plain functions that are stored in a session hash table the keys of which are unique identifiers. This way they can be called by the browser.
When Javascript is available actions will be called via AJAX, otherwise a normal request will be initiated.
It is often useful to set up closures as actions.
Example of typical action usage:
(defwidget counter () ((count :accessor count-of :initform 0))) (defmethod render-widget-body ((widget counter) &rest args) (with-html (:p (esc (format nil "The counter is at ~D." (count-of widget)))) (:p (render-link (lambda (&rest args) ;; closes over WIDGET. (incf (count-of widget))) "Count up!"))))
Dispatchers are widget that configure themselves and their children (i.e. broadly speaking the widget tree) based on the URI of the request.
Navigations are dispatchers that maintain a simple one-to-one association between
an URI token2 and
a widget.
/foo/bar/quuxthere are three tokens foo
Note that each widget can also in turn be a navigation that consumes an URI token, thereby building a many-to-many relationship between tokens and widgets.
The function make-navigation is a convenience frontend for building navigations.
The first argument is a title for the navigation. The rest of the arguments map tokens to widgets:
make-widget is applied to the widget argument so you can use strings and function designators3 instead of widgets in this context.(defun init-user-session (root) (setf root (make-navigation "Food Shop" "Fruits" (make-fruit-navigation) "Vegetables" (make-vegetables-navigation) "Billing" (make-instance 'billing-widget :shop-type 'food))))
Views provide convenient ways to define how data object are to be rendered.
Weblocks currently comes with three types of views: data views, table views and form views.
Form views are especially useful because they let you build forms in a declarative manner, error checking and reporting included.
Views mainly consist of a rendering framework unique to the
view4
and a series of view fields.
View fields usually map to the slots of your data class but they are flexible enough to be used in any other way. Associated with each view field regardless of the view type are things like a human-readable label and information on how to render the field (presentation).
Form views include additional parameters like how to translate user input to a proper model value (parser) and constraints on the data (satisfies).
Weblocks offers a storage framework with several backends. At the moment the supported backends are Elephant, cl-prevalence, CLSQL and in-memory storage.
You are free to use your own storage mechanisms, however.
Appendix C offers a comparison of the different stores.
One Lisp image may serve multiple web applications at once.
The target web application of a request can be determined based on the hostname, the port or the URI for example.
You don't have to worry much about this if you only intend to run a single application.
Weblocks comes with CL-WHO and Parenscript, two powerful packages that provide Lisp compilers for HTML and Javascript.
This means no more munging around with strings, which in turn means
Javascript may still be written as simple strings however. This comes in handy sometimes when working with a larger example copied from a Javascript library manual.
And you're free to use other facilities as you prefer.
For example YACLML5
is a function-based HTML generation facility that you might want to take a look at.
Some neat features of Weblocks are built on continuations. But don't worry, you will only need some high-level understanding of continuations to use these features along with some rules of thumb.
Most widgets consist of two main parts:
Map over the currently active (by URI) widget tree:
render-widget -> render-widget-body (widget-update-children?)
Forms enable the user to communicate with a web application.
Usually the server side action boils down to selecting, modifying, creating or deleting data sets (this is sometimes abbreviated as CRUD: Create/Read/Update/Delete)
Building web forms is usually a cumbersome process. Elaborate but complicated
solutions have been devised6,
but so far we haven't found any of them to match the ease of use and flexibility
of Weblocks' declarative view DSL.
The form mechanism consists of two parts, the dataform widget and the form-view view type.
Forms are usually built by defining form views using the defview macro and instantiating a dataform object with this view.
Let's define a view for creating and editing bug reports.
Let the model be defined thus:
This view should apply to users that are not developers: they may enter a summary and body but that's it.(defclass bug-report () ((id :type integer :initform (create-unique-id)) (status :type (member :new :open :resolved) :initform :new) (summary :type string) (body :type string)))
The summary and body fields will default to strings; every form field is presented as a text input and parsed as a string by default.(defview bug-report-form-view (:type form :inherit-from '(:scaffold bug-report) :caption "Enter a bug report") (id :hidep t) (status :hidep t))
Let's use this view to derive the view for developers:
The status field will automatically be displayed as a dropdown control since the scaffold inspector has decided this upon the slot's type.(defview bug-report-form-view/developer (:type form :inherit-from 'bug-report-form-view ) (status :hidep nil))
You can define scaffolding rules for your own types.
As part of the validation process Weblocks will also check whether a user input matches the slot's type regardless of whether you use scaffolding or not.
But let's assume that we want custom labels for the dropdown:
(defview bug-report-form-view/developer (:type form :inherit-from 'bug-report-form-view ) (status :hidep nil :present-as (dropdown :choices '(("This report is totally new, don't trust it!" . :new) ("Yeah okay, we're working on it." . :open) ("We've solved that problem already..." . :resolved)))))
quickforms are specialized dataforms.
They provide a way to build forms based entirely on a view; they are handy for operations where you don't have the user working on an actual model instance.
Let's dive right into it:
The macro defview is a declarative DSL for defining views.
Syntax:
(defview (NAME [:type TYPE] [:inherit-from INHERIT-FROM]
[:satisfies SATISFIES] VIEW-KWARGS...)
[FIELD-NAME
| (FIELD-NAME [:type FIELD-TYPE] [:initform INITFORM]
[:present-as PRESENT-AS] [:parse-as PARSE-AS]
FIELD-KWARGS...)]...)
In the above form, these metasyntactic variables have the following values:
Evaluated.
Evaluated.
Argument semantics:
However, field-kwargs keywords present in *custom-view-field-argument-compilers*' are not included in the call to make-instance; see that variable for information on how those are transformed.
The built-in custom view fields are as follows:
Only to replace widgets and remember how to restore the original state. XXX VERIFY
At the moment only CL-WHO is supported out of the box, but YAML and others may also be used.
examples to point out pitfalls
Every GF is a hook: subclass, :before, :around, :after
Not only useful for those wishing to hack the internals but also to gain an understanding of Weblocks that aids in debugging hard problems.
The whole tree update protocol goes like this:
1) HANDLE-NORMAL-REQUEST calls UPDATE-WIDGET-TREE, which walks the tree using WALK-WIDGET-TREE starting at ROOT-WIDGET and calling update-children at every node.
2) The selector's UPDATE-CHILDREN method (valid for all selectors, i.e. widgets that process URI tokens) calls GET-WIDGET-FOR-TOKENS.
3) if a widget corresponding to particular URI tokens is found, UPDATE-CHILDREN calls UPDATE-DEPENDENTS, so that the selector (or its subclass) may update its dependents list and do other housekeeping. The default implementation of UPDATE-DEPENDENTS just calls (SETF WIDGET-CHILDREN) to store the children under the :SELECTOR type.
Usually the only thing you'll want to do if you are implementing your own kind of selector is to subclass selector and provide a GET-WIDGET-FOR-TOKENS method for it. See class ON-DEMAND-SELECTOR for an example."))
(defgeneric get-widget-for-tokens (selector uri-tokens)
(:documentation "Given a list of URI tokens, map them to a widget. All selectors implement this method. There can be multiple strategies for mapping URI tokens to widgets: static maps, dynamically-generated widgets, dynamically-generated widgets with caching. Returns a widget or NIL if not found. Modifies URI-TOKENS.
views are the second highest level mechanism (after forms) in the weblocks system of forms, views, presentations & parsers that form the rendering framework for structured data. views are defined using defview.
defview is a macro that in turn calls defview-anon. defview-anon is not visible outside weblocks — if you have to use it, call it with weblocks::defview-anon. defview-anon actually makes the view, defview attaches a gensym'd name to it and stores it in the global views hash.
so all views are global, and any changes made to one show up in all the others (at least after a refresh of the browser).
See tutorial at http://trac.common-lisp.net/cl-weblocks/wiki/WeblocksDevelopment
I recommend the following SBCL initialization file (on UNIX systems it is ~/.sbclrc):
rlwrap (http://freshmeat.net/projects/rlwrap/) clbuild; stale FASLs (defmethod asdf:perform :around ((o asdf:load-op) (c asdf:cl-source-file)) (handler-case (call-next-method o c) (#+sbcl sb-ext:invalid-fasl #-(or sbcl allegro lispworks cmu) error () (asdf:perform (make-instance 'asdf:compile-op) c) (call-next-method))))using aclrepl (see also (http://www.sbcl.org/manual/sb_002daclrepl.html))
TRACE (beware builtin funs), BACKTRACE, DESCRIBE If no errors show up (sometimes occurs e.g. when working with continuations!) try CATCH-ERRORS-P
detachtty screen tmux remote swank
Different stores have different merits. Here's a short comparison.
This library embodies the prevalence database strategy: Everything is kept in main memory; all writes are logged to disk, and the in-memory database is restored with the aid of the log at initialization time.
Prevalence is a great library to start with because you can do carefree prototyping and also run production sites.
Important note: The original version of cl-prevalence has some bugs that prevent is from working reliably when used with Weblocks.
It is recommended to use the fork at http://bitbucket.org/skypher/cl-prevalence/ where this problem has been remedied.
Elephant is based on a key-value database model (like SQL servers) It offers convenient CLOS integration and decent speed.
Not as easy to set up as cl-prevalence, but a great library to work with.
The CLSQL store relies on the popular SQL ecosystem. This means proven client-server management systems like PostgreSQL, hosts of excellent tools and great interoperability.
On the negative side SQL has an impedance mismatch when matched against object-oriented data structures.
You cannot use CL as a query language either, CLSQL only provides a Lispy layer over SQL.
| Weblocks | UCW/AJAX | Seaside | PLT Webserver | RubyOnRails | Django | |
| Language | Common Lisp | Common Lisp | Smalltalk | Scheme | Ruby | Python |
| Supports REST | Yes | Yes | Yes | Yes | ||
| Supports multi-tier dispatch | Yes | |||||
| AJAX support built-in | Yes | Yes | No | No | ||
| Degrades gracefully without AJAX | Yes | No | (N/A) | (N/A) | No | No |
| Scaffolding/DRY | Dynamic | No | Static | |||
| Support for non-SQL databases | Yes | Yes | No | |||
| Interactive debugging | Yes | Yes | Yes | Yes | No | No |
| Bundling/compression built-in | Yes | No | No | |||
| Community | Tiny | Tiny | Small | Small | Medium | Medium |
| Community support | Yes | Yes | Yes | |||
| Commercial support | Yes | Yes | Yes | Yes | Yes | |
| License | LLGPL |
Weblocks
Django: read basics at djangobook.com
Django works at a level similar to hunchentoot. A series of urls is mapped to functions. Templates can be used to fill in html.
Uses mvc.
Python: Twisted?
Google App Engine?
Rails: Uses mvc. Ruby has some nice language features. Framework is backwards, and forces you to repeat yourself many times. A guiding
principle is to not repeat yourself (colloquially DRY) and in combination with a poor DSL it can be quite gibberishy. Strongly tied to SQL as data store.
Ruby: Other?
Perl: Mason?
PHP: Cake?
Seaside