Setting up the environment (and a hello world on the side) <- back | toc | forward -> Yaclml tags
Basic ucw mechanics
In the course of this chapter we will take a flight through website-making history. We begin with traditional website-making techniques and we will quickly work our way up. In the process you can't help but be introduced to the basic building blocks of a ucw program: components, actions, entry-points, applications and render methods. Ready made source is available.
Components, render methods, applications and entry points
Components
Component have more than one role. First they act as your basic storage unit. They are classes and you can define them in the same way, but they have added functionality given through meta object protocol modifications. To explain these modifications we need some background on the inner workings of ucw: When ucw receives a http request it checks if the request is a part of user session already in progress. If not it creates a new one. Such a session is made up of frames. Every new request makes a new frame which is referenced by a random string. Every frame copies the values of relevant components and their slots from the frame before, except of course when the values of a given class have changed. If a user wants to go back to a previous page, the appropriate frame is looked up with the help of the identifier string (be it through get or a cookie), and the original values are restored. So frames are the memory of a ucw session. Some of the modifications made to components facilitate this process of getting and setting values in frames under the cover.
Components are also clothes-hangers for html output. You generate html by defining render methods on them. In the case of our example the method renders (as you say) the simple-window-component hello-world, which is a pre-defined component to create an empty canvas. You can nest render methods of other components, in which case you piece the data together to form a complete page. With this information we can step through conf.lisp and hello-world.lisp from the last chapter:
config.lisp:
(in-package :ucw-intro)
(defvar *www-root* (merge-pathnames (make-pathname :directory '(:relative "www")) (asdf:component-pathname (asdf:find-system :ucw-intro))))
(defparameter *ucw-intro-application* (make-instance 'cookie-session-application :url-prefix "/ucw-intro/" :www-roots (list *www-root*) :dispatchers (list (action-dispatcher) ;; hello world (url-dispatcher "hello-world.ucw" (call 'hello-world))) :debug-on-error t))
(in-package :ucw-intro)
hello-world.lisp:
(defcomponent hello-world (simple-window-component) () (:default-initargs :title "hi" :stylesheet '("sheet.css" "sheet2.css") :content-type "text/html; charset=utf-8;" :javascript '((:src "dojo.js") (:js (dojo.require "dojo.event.*")) (:script "var foo = 3;"))) ;; (:entry-point "hello-world.ucw" (:application *ucw-intro-application* :class url-dispatcher)) (:render () (<:as-html "hello world")))
Applications
First we define the filesystem directory which is going to function as www-root for static data such as images, style-sheets and the like. We feed it to the definition of our first application object. An application is an arbitrary conceptual construct with which you define a number of options for a block of web-pages/site-logic. It lets you specify if you want to use cookies, what charset you want to use, etc. A quick tour of the chosen options: In this instance we want to use cookies, which means that ucw starts searching for certain cookie information after it has looked for information in other places. The url prefix is the browser path relative to the server root. Www-roots is a list of pathnames the application searches to get to static data relative to the the APPLICATION root. They are set relative to the url-prefix, eg in this example from the outside the www-root of ucw-intro is 127.0.0.1/ucw-intro/. Debug-on-error will or will not throw you in the debugger when things go wrong in this application, depending on if it is set to t or nil. The dispatchers we will cover later on.
defcomponent and render methods
The simple-window-component class and the defcomponent macro can together be seen as as a convenience layer that constructs the underlying building blocks a standard application wants to have. In this section you will learn the semantics of both and you will learn how to define the building blocks without them.
A simple-window-component sets up your basic html layout. You can feed a title, stylesheets, content-types, and, in various ways, javascript to the :default-initargs argument of defcomponent using a suitable keyword for each one used. All of them accept a list as well as a basic unit suitable for the keyword, although you probably don't want to pass a list to the title argument as that will just print a quoted list. You can specify one or more javascript source files with the :src key; using :script lets you insert javascript as is; and :js lets you insert javascript in parenscript syntax. With this information defcomponent sets up a whole page, except for the body. That's left for the render method of the component you are defining.
Defcomponent defines components almost the same as defclass defines classes. :default-initargs, slots, etc... are simply passed to the underlying defclass. But it has some extra options. You can supply a default backtrack function with :default-backtrack, you can supply a render method with :render, you can supply an entry-point with :entry-point, and you can supply an action with :action. More on those last two in the next section, and more on :default-backtrack in a future chapter.
As stated before, a render method is a regular method that specializes on a given component to output html. It is called when the program decides it is time to put together a page to show the user. More on program flow in the next section of this chapter. The defcomponent macro passes the arguments from the :render form to a defmethod and let's it specialize on the name given to defcomponent. If no name is given the name defaults to the name given to defclass. There are two standard ways to output html in a render method. You can use the <:as-html (or <:ah) macro and just type big slabs of html-escaped text or you can use <:as-is (or <:ai) to produce un-html-escaped text. But you know as well as I do that lispers would rather be tortured for weeks by the US government than not lispify foreign syntax. UCW uses yaclm for this, which is the in-house html markup lispifier. Html tag names start with <: and each attribute is a keyword followed by its value. The standard html format that UCW spits out is XHTML 1 (i.e. html 4 but stricter). The whole of XHTML 1 is defined in yaclml as is SVG 1.1. More explanations in the yaclml chapter.
Without defcomponent and simple-window-component you could have written the code like this:
(defclass hello-world () () (:metaclass standard-component-class))
(defentry-point "hello-world.ucw" (:application *ucw-intro-application* :class url-dispatcher) () (call 'hello-world))
(defmethod render ((hello-world hello-world)) (<:as-is "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/transitional.dtd\">" #\Newline) (<:html (<:head (<:meta :http-equiv "Content-Type" :content "text/html; charset=utf-8;") (<:title (<:as-html "hi")) (<:link :rel "stylesheet" :href "sheet.css" :type "text/css") (<:link :rel "stylesheet" :href "sheet2.css" :type "text/css") (<:script ;; most browsers (firefox, safari and ie at least) really, ;; really, really don't like empty script tags. The "" forces ;; yaclml to generate a seperate closing tag. :type "text/javascript" :src "dojo.js" "") (<:script :type "text/javascript" (<:as-is (js:js* '(dojo.require "dojo.event.*")))) (<:script :type "text/javascript" (<:as-is "var foo = 3;"))) (<:body (<:as-html "hello world"))))
What we have omitted in this example is giving the components extra slots. Declaring slots in components is like defining slots for regular classes with two extra options, which are :component and :backtrack. The former is for nesting components, the latter for backtracking values into other frames. Both will be discussed in future chapters.
Entry points, actions, forms and control flow
You might have heard some fellow that touted UCW as being hard. Well, the hard part is to figure out how it ticks. But we don't need to know that right now. We just need a working model of basic control mechanics.
First to clear some confusion. You have already seen the :dispatchers part of cookie-session-application, the commented out :entry-point part of defcomponent and the commented out defentry-point. They all do the same thing. :entry-point and defenty-point are the old way of adding a new page to an application. The :dispatcher way centralizes things a bit and is intended to replace defentry-point. The old way was reintroduced as a macro so that existing applications will not be broken too much. In this tutorial :dispatchers is used as the default in explanations and commented out defentry-points and :entry-point show the alternatives.
When a user calls a ucw page for the first time without being redirected by another UCW page and without information from some UCW cookie that hasn't timed out yet, the machinery checks the last form you just typed at the :dispathers part of your application constructor, which, in our example, is (call 'hello-world). This form calls the component hello-world, which means it locks in that component as the main component to be rendered to the user. Whenever UCW gets user input it makes a new frame and after the preliminaries are over, it calls the render method on whatever component is at that moment the main component and causes the page to be served. The end part of the :dispatchers thingy and so-called `actions' handle the control flow of a UCW program between page calls. You can put any number of forms into those parts; the forms will be parsed as a unit.
By the way, at the end part of defentry-points in our example you have an empty form just before the last one. We will turn to it later.
On the face of it that call to hello-world doesn't seem to do much out of the ordinary, and that's the whole point. As you might know the web poses some problems for standard program execution because web-pages cut the flow into pieces. Those pieces have to be pieced together again somehow. It turns out that cps (continuation programming style) is quite handy for this purpose. Basically it means being able to capture the state of your program anywhere you want in a function. You can call that function again whenever you want, and however often you want, to pick up the execution of your program where you left off. Some languages have facilities built in to just capture a program state like that, but lisp doesn't. You have to transform the code to be able to do this. You could do this by hand if you were mad, but UCW has a cps transformer onboard that does it for you. This information might be a bit vague. If you want to read up on the subject I suggest you read chapter 20 of On Lisp.
Now all of this transforming is done under the bonnet (or hood) so you won't have much to do with cps transformation, but there is actually also a practical purpose for knowing this, and to explain that we need yet some more theory. A cps transformer must sort of understand the semantics of the code it's working with so it needs a code walker to interpret, for example, whether a particular form is a macro and how to keep track of environment variables. Every implementation obviously has a code walker but language users don't have access to it. You have to roll your own and UCW did. But walking code ain't as easy as it sounds. Code walkers usually don't capture lisp perfectly and the UCW code walker is no exception. Usually stuff works out fine but don't be surprised if your code doesn't do what it should at the end of a dispatcher in the :dispatchers list or inside an action. When you have to handle a lot of code inside an action, try to delegate as much as possible to function calls, because functions outside of an action are not subject to the laws of an alternate lisp universe.
Just some more theory so you will fully appreciate the example below. At some point a dispatcher should call a component with (call ... , which initializes the component with initargs passed to it. Then the component is set as ''the'' component for that frame. It is rendered, perhaps together with the sub-components that its render method causes to be rendered. When the user is presented with a page, she/he gets her/his first chance to interact with the program through the usual mechanics: forms, links, and buttons. The user clicks or writes something and another request gets sent to the webserver. The traditional (some whisper 'pre-ucw') way to handle a request is to check GET and POST values and act upon them.
UCW provides this facility, as well as a more convenient facility we shall now describe. Note that in our example the form before the call to hello-world (in the code above, just below the words 'Without defcomponent and simple-window-component') is empty. In that form you could instead specify which parameters from the page you want to use. If you want to give a parameter a default value you can put the parameter and its default between parentheses. These values will then be available for use in the forms you put after that one.
But of course UCW has more tricks up its sleeve. For some html tags UCW uses macros that have names beginning '''<ucw:''' and completed by the name of the tag; these html tags are a, area, button, form, select, option, textarea, and input. For convenience UCW gives the form input attributes '''text ''', '''password''', and '''submit''' their own <ucw: macros. We shall simply call these ''''tag macros''''. Beyond these tag macros, UCW implements other <ucw: macros that, in UCW, work like the tag macros: integer-range-select, month-day-select, month-select, render-component, and script. These last ones we will cover in the advanced forms chapter under the '''select''' section. See this tutorial's examples of using <ucw: tag macros in containers.lisp (one example) and basics.lisp (several examples).
Extended functionality of the <ucw: tag macros comes from the optional keyword arguments you can pass them: the :action, :accessor, :reader, and :writer arguments. One or more of these keywords apply to any particular <ucw: tag macro, but only one can one them can be used in a single call to the macro. With the :action keyword you can specify a form to be evaluated with the relevant parameters when the specified user-input event occurs. With :accessor you can specify a place. The value of that place becomes the initial value of the specified html tag. If the tag returns a value, the place will store it. :reader and :writer are used as a team to provide more flexibility than :accessor does. If provided, the value of :reader is used as the initial value of place. The value of writer should be a function which accepts one argument: the value returned by the tag. [Tom Elam asks: Is this true? Must it be a functions?] We will rehash this and add explanations in the yaclml chapter.
Continuing our control flow we assume the user uses one of the before-mentioned UCW-controlled input forms to once again bother the server with information. If the user hasn't submitted one of the ucw forms with one of those so-called actions attached to them, the current component stays in place. When a new page is rendered, the render method will again be specialised on this component. When an action is called it can do a lot of things, but seen through this prism the possibilities all fall into one of three cases: the action executes code without ever using the `call' macro to call to another component, it can somewhere along the line call another component, or it can answer a component. As long as it never calls
(call ...
or
(answer ....
the component stays in place. When it calls another component, the called component gets locked in as the main component and UCW stores a continuation of where in the present main component the action got interrupted. When an action answers, UCW restores the component that was the main component before it called the component that just answered. Thus, by Voodoo Majik, the return makes it appear just as if a function call or interrupt were terminated by the return of that function or interrupt. The continuation that was stored in that component is called with an optional value that is the value of the call form. The action in which the call was made continues until it either calls a component, answers a component or ends, in which case the component connected to the action gets rendered.
'But how should i understand dispatchers', you might wonder. Well, dispatchers are called when UCW tries to figure out what to do with an incoming request. You specify what UCW should look at when you construct an application (or when you use a defentry-point. You can use them in conjunction.) Action-dispatcher is checked first. It will check if an action parameter is present in the url. If so it will try to look if it can match it against a relevant action and if it finds one it will execute it. Furthermore you should add a url-dispatcher or a regexp-dispatcher to the list for every page you want to use as an entry-point into you application. You specify url-dispatcher or regexp-dispatcher for plain url names or regular expressions relative to the application root, the string of the url or the regular expression and the set of forms you want it to execute, which should at some point at least call one component to render. Since the dispatchers (and entry-points) are the entry-points into your application they have no component to answer to. When the set of forms finishes it returns to the first form, and again, and again, e.g. they are in an infinite loop.
It's high noon for an example. Add these to the dispather list of your config.lisp file:
;; basics (regexp-dispatcher "^(basics.ucw|)$" (call 'basics)) (regexp-dispatcher "^(get.ucw|)$" (with-request-params ((cow "moo")) (context.request *context*) (let ((cow (concatenate 'string "mighty " cow))) (call 'basics :cow cow))))
Add this to your ucw-intro.asd file:
(:file "basics" :depends-on ("config"))
And define the file basics.lisp like so:
(in-package :ucw-intro)
(defcomponent basics (simple-window-component) ((cow :initarg :cow :accessor cow :initform "aspacia") (pig :initarg :pig :accessor pig :initform "knorrie")) (:default-initargs :title "hi" :stylesheet "sheet.css") (:render () (with-slots (cow pig) basics (<:as-is "in the old days, to update the name of your lovely cow named <b>" #\Newline) (<:as-html cow) (<:as-is "</b> you would update it like so: ") (<:form :action "get.ucw" :method "GET" (<:text :name "cow") (<:submit :value "name your cow")) (<:p (<:a :href "get.ucw" "go to get without any value to get to the default value of the entry point")) (<:p (<:as-html "but we have left them behind. In the brave new world we will update the name of our lovely pig like: ") (<ucw:form :method "POST" :action (refresh-component basics) (<ucw:text :accessor pig) (<:submit :value "name your pig"))) (<:p (<:as-html "to pass a value and control to another component we do like so:") (let ((stuff-to-say "")) (<ucw:form :action (get-stuff-to-say basics stuff-to-say) :method "POST" (<ucw:textarea :accessor stuff-to-say) (<:br) (<:submit :value "have something to say")))))))
;;(defentry-point "get.ucw" ;; (:application *ucw-intro-application*) ;; ((cow "moo")) ;; (+ 2 2) ;; (let ((cow (concatenate 'string "mighty " cow))) ;; (call 'basics :cow cow)))
(defaction get-stuff-to-say ((basics basics) stuff-to-say) (let ((stuff-to-say (concatenate 'string stuff-to-say " (boring!!!)"))) (setf (cow basics) (call 'the-other-one :stuff-to-say stuff-to-say))))
(defaction ok ((c component) &optional (value c)) (answer value))
(defcomponent the-other-one (simple-window-component) ((stuff-to-say :initarg :stuff-to-say :accessor stuff-to-say)) (:default-initargs :title "hi" :stylesheet "sheet.css" :content-type "text/html; charset=utf-8;") (:render () (<:p (<:as-html "- "(stuff-to-say the-other-one))) (<:p (<:as-html "yeah, very 'enlightning'. Dazzle me some more: ")) (<ucw:form :method "POST" :action (refresh-component the-other-one) (<ucw:textarea :reader (stuff-to-say the-other-one) :writer #'(lambda (x)(setf (stuff-to-say the-other-one) (concatenate 'string x " (boring !!!!!)")))) (<:br) (<:submit :value "have something to say")) (<:br) (<ucw:a :action (ok the-other-one "nice to be back") "back to the last component")))
First we observe the added slot to the basics component called cow. We use it to store the values passed by our forms. The first form shows the old way: The name cow is picked up by the relevant url dispatcher, which we also let calculate 2 + 2 for no reason at all and in which we add mighty or puny to cow. The link below it shows that if no parameter named cow turns up the value of cow is set to moo. Notice however that if an empty form is submitted the name of cow is set to zero and our cow is nameless but still mighty or puny. Our second form has to be an ucw form if we want the <ucw:text tag to work. The text tag only needs :accessor and a refresh-component action to work correctly; no need for pages to redirect to and no need for a name. The submit button could have been omitted, just as with the normal form. The :accessor method circumvents the entry-point for the value of pig and the refresh component action refreshes the current component. The form method could have been either get or post. For the correct working of ucw it doesn't matter which you choose. If you click the back to entry point link, you pass control back to the entry-point action. which proceeds to it's next call. The ok defaction is already provided by ucw.
The third form shows how to pass control to another component. We (I at least) want to fill the slot stuff-to-say of component the-other-one with a value passed by the form. Since they of course don't share the same slot we make a temporary variable to have something to hang on to. When we submit the form we execute the action get-stuff-to-say which passes control to stuff-to-say with the slightly modified stuff-to-say. So now we can contain the generalized behavior of an entry-point within a function which can be tailor-made to fit one tag. Notice it doesn't matter if you call the next component in either hello-world.ucw or get.ucw. The only interesting thing in the form on the the-other-one page is that it uses reader and writer. The link below it gives control back to basics. As you can see you can give ok and answer an extra value which will be the value returned by the call form. Notice the value of pig is how you left it. If you hadn't changed the name of your cow it would also still be the same.
Btw, as you can see, the way to specify parameters to be picked up in a dispatch method is a bit more complicated than when you use a defentry-point. The defentry-point simply lets you specify the parameters in it's third argument. The parameter is transformed into the with-parameters macro that is called in the dispatch way. The parameters can be supplied as a symbol if you don't care about giving it an initial value or as a list of a symbol and a string if you do want to give it an initial value.
There you have it. You mastered the basic ucw mechanics, but of course you would like some more tools in your kit to let ucw work on code in stead of you. As you saw in the index: templating, extended form handling, login code and more is provided. This kind of functionality is the subject of the next few chapters.
Setting up the environment (and a hello world on the side) <- back | toc | forward -> Yaclml tags