Weblocks Tutorial This assumes you know web programming. It may also assume you understand continuations. Continuations are used in Weblocks to let you write a request handler like this: Imagine wanting to pause request handling to ask a user another question first: (defun handle-request (r) (let ((choice (query-user "blue/red pill?"))) (render "you chose ~A" choice))) query-user, render are made up functions. query-user hides something unusual. Mid-way through completion, it pauses execution, sends a stream of bytes to the web browser containing an html form with a question, waits until the user answers it, then that http request causes execution to resume. The pause involves packaging up the state of execution and storing it away for later retrieval. ---- I'll assume you're an expert programmer, but don't yet know how to use Weblocks. It took me a while to understand it. That's the time I hope to save you. I already knew Seaside in Smalltalk well, which is also continuation based. I don't use objects. This won't show you automatic persistence of objects. Its focus is on the web layer. (ql:quickload '(weblocks)) (use-package '(weblocks)) (defun entry (c) (setf (composite-widgets c) (list "hallo world"))) (defwebapp tute :init-user-session 'entry) (start-weblocks) ;go to http://localhost:8080/tute to see "hallo world" A visitor to your site gets an http session. Weblocks creates a composite widget representing what they see. Above, #'entry is called to initialize it. We initialize it to a list with only one widget. Of type string. Functions are another type of widget: (defun w1 () (with-html (:h1 "hallo world"))) (defun entry (c) (setf (composite-widgets c) (list #'w1))) (reset-sessions) ;reload http://localhost:8080/tute ;change the body of w1 (defun w1 () (with-html (:h1 "hi again"))) ;reload web page ;change not reflected ;why not? Your http session is the same old one. #'entry isn't called on every request, just when a new http session is set up. Further requests just render the same composite widget object hierarchy that that web user already has. Different web users have different instances of the composite widget hierarchy. That top level component can change state (independently for each user). (reset-sessions) Now refresh to see "hi again". #'entry needed to be called after we threw away all weblocks's old sessions. Function widgets like #'w1 above get called every time Weblocks is rendering a user's composite widget tree. Its body is expected to write bytes to the http response stream. We use the cl-who library to help us type our html. As we go along, I'll explain those aspects of cl-who that you can't guess. ---- Here's a web form: (use-package 'cl-who) (defun w1 () (with-html-form (:POST (lambda (&key name &allow-other-keys) (setf (webapp-session-value "u") name)) :use-ajax-p nil) (fmt "hallo ~A" (webapp-session-value "u")) (:br) "what's your name?" (:input :name "name" :type "text") (:input :type "submit"))) (reset-sessions) ;refresh web page The lambda is invoked on form submission. Its key arguments are named after the form's input fields. The lambda's body here stores the user's name into a new http session variable on the server called u, which is initially nil. We turn off ajax to be able to see updated html after form submission. --------- do-widget provides the pause/resume feature I described. Below, it replaces the current component with another. When that one returns, it swaps the original component back in and resumes it. I use it in form handlers like the lambda above, and in anchor link handlers. First a technicality. The proper way to make widgets from things like functions appears to be: (make-widget #'my-function) not just #'my-function even though that's what we've done so far and Weblocks has figured it out. Here it doesn't figure it out, so I'm going to be explicit. Okay, to have Weblocks swap widgets as I described, I find I need to insert one composite widget between the top level one and my own set. So let's rewrite the entry point: (defun entry (c) (setf (composite-widgets c) (list (make-instance 'composite :widgets (list (make-widget #'w1)))))) ;that innermost list tends to hold all the widgets making up my page. (reset-sessions) And now we're set: (defun w1 () (let ((c *current-widget*)) (with-html "this is the top level" (:br) (render-link (lambda/cc (&rest noclue) (do-widget c #'sub1)) "click here" :ajaxp nil)))) (defun sub1 (k) (with-html "you are now in the subroutine" (:br) (render-link (lambda (&rest noclue) (answer k 'anyresult)) "return" :ajaxp nil))) ;refresh the page I store the current-widget away. When "click here"'s lambda is invoked, do-widget is called to replace it with another widget. A function widget. Function widgets need to take a parameter, k, to be callable from do-widget. k represents a continuation that can be resumed later by calling answer. The user can hang about inside sub1. Refreshing the page will keep him there. Clicking the "return" link will invoke its handler, which we've written to replace sub1 with the original w1 via answer. This also resumes execution of the calling handler right after the code that calls do-widget. (There is no more code in this case). ;I turn off ajax because ajax is not my focus today ;I don't know why forms want :use-ajax-p and render-links want :ajaxp Lisp doesn't have continuations. There is a library, cl-cont, that restructures your code to get the same effect. That's why we use lambda/cc above. Use it or defun/cc for any function containing a do-widget. Then that code will be restructured. Exercises 1. Write a function req-text that displays a textarea. On submission have it answer the submitted text to the function that called it. 2. Have the calling function repeatedly call req-text until the answer is non-empty. Then have it display the character count. 3. Ask the user for a number, then another number, and show him the sum. -- Send any feedback to vibhu at the domain wispym.com July 2015