Component Oriented UI

Last post I introduced UCW and how to get started by creating a hello world page. However, in that example we were crudely using FORMAT to outtput a string to an HTTP response stream.

Today we will be rendering HTML in a much more expressive, reusable, and abstracted way by using UCW's component-oriented UI framework.

To illustrate this, we will create a web page that displays an HTML form that will simply open your default mail program and send its values. Kind of cheesy, and pre-CGI times, but hey, I don't want to distract today's subject with persistance libraries or anything like that for now. I want to focus on components as they are a basis for Uncommon Web applications.

Introducing UCW Components

Simply put, UnCommon Web components are instances of CLOS classes with the STANDARD-COMPONENT-CLASS metaclass. However, UCW provides a macro for defining component classes, giving us some syntactic sugar. The following two forms are thus the same:

(defclass example-message ()
((message :accessor message
:initarg :message
:initform "World!"))
(:metaclass standard-component-class))

(defcomponent example-message ()
((message :accessor message
:initarg :message
:initform "World!")))

I will be using the defcomponent in my examples as it is obviously more concise, and it abstracts the MOP aspect of UCW from our view. If you want to learn more about the CLOS MetaObject Protocol, visit:
http://www.alu.org/mop/index.html

Uncommon Web defines some standard components that we can use to define our web app—such as, window components. "WINDOW-COMPONENTS are the top-level wrappers. Generally, they print the html header and then delegate to another component for the rest."

If we dive into UCW's standard components, we find the following fun inheritance chain:
STANDARD-WINDOW-COMPONENT -> BASIC-WINDOW-COMPONENT -> BASIC-WINDOW-FEATURES-MIXIN + WINDOW-COMPONENT

STANDARD-WINDOW-COMPONENT
Specializes BASIC-WINDOW-COMPONENT by adding a :body slot. The body is mainly used for nesting components as shown by the use of the :component t option. Remember that when nesting components, the slot that contains the component should be marked :component t, as the STANDARD-WINDOW-COMPONENT does:

(defcomponent standard-window-component 
(basic-window-component)
((body
:initform nil
:accessor window-body
:component t
:initarg :body)))

BASIC-WINDOW-COMPONENT
No slots are defined but it inherits the BASIC-WINDOW-FEATURES and WINDOW-COMPONENT superclasses. For convenience, apparently. :)

(defcomponent basic-window-component
(basic-window-features-mixin window-component)
()
(:documentation
"A convenience class for writing window components."))

BASIC-WINDOW-FEATURES-MIXIN
The name is pretty self-explanatory, as it defines slots for the following features of a window (descriptions are from the documentation strings):

  • Title
  • Stylesheet: "The URL of the css file to use as a stylesheet for this window."
  • Icon: "Optional URL for an icon."
  • doctype: "Doctype for this window."
  • content-prologue: "Unless nil it's printed <:as-is before any other output. Suitable for <?xml...?> lines."
  • html-tag-attributes: "A yaclml attribute list that'll be rendered into the <:html tag's attributes."
  • javascript: "List of javascript includes."

WINDOW-COMPONENT
These components are the top-level wrappers. Generally, they print the html header and then delegate to another component for the rest.

  • content-type: "The Content-Type header for the http response (also used in the meta tag)"

Defining custom components

So let's go ahead and define a new component that subclasses STANDARD-WINDOW-COMPONENT:

(defcomponent orders-window (standard-window-component)
()
(:default-initargs
:title "Book Order Form"))

Notice how we do not define any new slots, but rather supply a default initarg called :title, which refers to the title slot defined in BASIC-WINDOW-FEATURES-MIXIN.

Now let's create two components that will encapsulate our form's display code. One will be for the form itself and the other for a dropdown list.

(defcomponent form-component ()
())

(defcomponent products-dropdown ()
())

Simple enough, eh? The magic happens when we specialize the generic RENDER function and we don't need to make these components generic enough to justify defining slots for them just yet. If you find yourself defining similar components over and over, you could probably create a more generic component that could be used as a plug-in widget or something where you wouldn't even have to write the HTML code anymore in order to render it.

Rendering components

Once a component is defined, we need to specialize the generic RENDER function for it. However, components derived from UCW's standard components--such as WINDOW-COMPONENT--already have RENDER methods defined. If you don't want to change how your window component is rendered, you don't need to specialize the generic RENDER function yourself. If you did, you could combine the superclasses' RENDER method by calling CALL-NEXT-METHOD somewhere in the body---unless you wanted to completely implement the desired behavior of the generic function (PCL ch 16), in which case you'd omit CALL-NEXT-METHOD.

For the time being, let's not specialize the RENDER function on our ORDER-WINDOW. Let's go ahead and define render methods for our form and our dropdown component:

(defmethod render ((form form-component))
(<:form :method "post" :action "mailto:felideon@gmail.com"
(<:as-html "Name: ") (<:text :name "Name") (<:br)
(<:as-html "Address: ") (<:text :name "Address")
(<:br)
(<:as-html "Phone: ") (<:text :name "Phone") (<:br)
(<:p) (render (make-instance 'products-dropdown))
(<:p) (<:submit :value "Place Order")))

(defmethod render ((products products-dropdown))
(<:select :name "Product"
(<:option :value "PCL" "Practical Common Lisp")
(<:option :value "C@W" "Coders At Work")
(<:option :value "OOPCLOS"
"OOP in Common Lisp: A Programmer's Guide to CLOS")
(<:option :value "AMOP"
"The Art of the Metaobject Protocol")
(<:option :value "GENTLE"
"Common Lisp: A Gentle Intro to Symbolic Computation")))

Notice how we're manually rendering the products dropdown by calling the RENDER method on an instance of PRODUCTS-DROPDOWN. This would normally never be done in UCW, since:

In general, UCW calls the RENDER method as part of the RERL. However, in this case we never pass control to a component (we haven't made it to control flow yet), so something like this is neccessary.

Finally, we define an entry point in order to get to our page:

(defentry-point "index.ucw"
(:application *orders-ucw-application*
:with-call/cc nil)
()
(render (make-instance 'order-window)))

Note that "The :WITH-CALL/CC option is set to NIL here. This is not strictly neccesary, but were not using UCW's continuation based features yet, so we can gain a minor performance increase by avoiding the overhead of the CPS interpreter."

UCW Method Combination

Another topic I want to touch on is regarding method combination---at least with respect to UCW. For a primer on what method combination is and how it works, I recommend reading chapter 16 of Practical Common Lisp.

UCW apparently uses a library, also written by Marco Baringer, called arnesi. In addition to other utilities, such as the CPS transformer, it provides a MOP compatibility layer (that pre-dates Pascal Constanza's closer-mop) that extends the standard method combination, like so:

"Same semantics as standard method combination but allows
\"wrapping\" methods. Ordering of methods:

(wrap-around
(around
(before)
(wrapping
(primary))
(after)))

:wrap-around, :around, :wrapping and :primary methods call
the next least/most specific method via call-next-method (as
in standard method combination).

You'll see these auxiliary methods used a lot in UCW's source code. I'm encouraged to encapsulate my rendering code like this as it appears to be a very malleable way to reuse and adapt display code.

Auxiliary methods are just a convenient way to express certain common patterns more concisely and concretely. They don't actually allow you to do anything you couldn't do by combining primary methods with diligent adherence to a few coding conventions and some extra typing. Perhaps their biggest benefit is that they provide a uniform framework for extending generic functions. (PCL)

So now let's refactor our form's RENDER method a bit by splitting it up using the standard method combination qualifiers:

(defmethod render :around ((form form-component))
(<:form :method "post"
:action "mailto:felideon+blog@gmail.com"
(call-next-method)))

(defmethod render :before ((form form-component))
(<:h1 (<:as-html "Book Order Form")))

(defmethod render ((form form-component))
(<:as-html "Name: ") (<:text :name "Name") (<:br)
(<:as-html "Address: ") (<:text :name "Address") (<:br)
(<:as-html "Phone: ") (<:text :name "Phone") (<:br)
(<:p) (render (make-instance 'products-dropdown))
(<:p))

(defmethod render :after ((form form-component))
(<:submit :value "Place Order"))

Note that CALL-NEXT-METHOD is required in the :around method, or it "will completely hijack the implementation of the generic function from all the methods except for more-specific :around methods." (PCL)

We didn't actually use the extended "wrapping" methods provided by arnesi this time, but I can picture a few scenarios where I would, such as using the :wrap-around method for wrapping a <table>.

How to remove a specialized RENDER method

There will be a time in your CLOS programming days where you will want to remove a method from a generic function. You can easily do this by using the Slime Inspector (thank you adeht).

For example, if we defined a RENDER method for any UCW component and wanted to get rid of it, all you need to do is start the inspecter (C-c I or M-x slime-inspect RET) and then type in, literally: (defgeneric render (component)) RET. Alternatively, you could use M-. on any method and you should see the DEFGENERIC form in the first line. At that point, once you call slime-inspect all you have to do is RET since the minibuffer will have the value.

Once you're in the *Slime Inspector* buffer, it is pretty clear what to do. Just place the cursor over the [remove method] link next to your component name, and hit RET.

Wrapping it up

So there you have it. We've started getting into real Uncommon Web application territory, as components are a basis for many of UCW's features.

[C]omponents play an important role when using UCW's advanced control flow features, and it is useful to abstract the RENDERing to a single method.

[...]

It must be said that components can do a lot more than encapsulate the display code. Components can call other components, which can answer with real lisp values. Components are also conveinent places to store data related to the application that one might traditionally keep in a 'session' variable.

If you want to compare or run today's final sample code, you can grab it at:
http://github.com/felideon/ucw-sample-code/blob/master/orders.lisp

To run it, remember to (require :ucw), (in-package :ucw) at the REPL, and then C-c C-k in the file to compile it. Call (startup-orders) in the REPL and then you should be able to browse to http://localhost:8080/orders/index.ucw to see the form.

I do apologize for the hiatus between posts, but between job hunting and the holidays I really hadn't had a chance to sit down and play with UnCommon Web much, much less write a blog post. You should follow me on twitter here as I often post updates of upcoming blog posts and tid bits regarding UCW.

References:
(PCL) Peter Seibel, Practical Common Lisp, Ch. 16
(where citation missing) Drew Crampsie, gettingstarted.txt

Filed under  //   common lisp   components   method-combination   render   ucw   ui   uncommon web   web development  

Comments [6]

About

Hi, my name is Felipe and I am a software developer aspiring to be a startup founder. I make a living developing C# web apps, but I enjoy hacking with Lisp in my free time.

Drop me a line at felideon+blog@gmail.com. You should also follow me on twitter here.