Added functions to set up and tear down a persistent compilation environment.
[clinton/parenscript.git] / docs / tutorial.lisp
CommitLineData
8e198a08
MB
1;;;# ParenScript Tutorial
2
3;;; This chapter is a short introductory tutorial to ParenScript. It
4;;; hopefully will give you an idea how ParenScript can be used in a
5;;; Lisp web application.
6
7;;;# Setting up the ParenScript environment
8
9;;; In this tutorial, we will use the Portable Allegroserve webserver
10;;; to serve the tutorial web application. We use the ASDF system to
11;;; load both Allegroserve and ParenScript. I assume you have
12;;; installed and downloaded Allegroserve and Parenscript, and know
13;;; how to setup the central registry for ASDF.
14
15(asdf:oos 'asdf:load-op :aserve)
16
17; ... lots of compiler output ...
18
19(asdf:oos 'asdf:load-op :parenscript)
20
21; ... lots of compiler output ...
22
23;;; The tutorial will be placed in its own package, which we first
24;;; have to define.
25
26(defpackage :js-tutorial
d5ede101 27 (:use :common-lisp :net.aserve :net.html.generator :parenscript))
8e198a08
MB
28
29(in-package :js-tutorial)
30
d5ede101
VS
31;;; Since we will not be using ParenScript's package system for this
32;;; tutorial, disable it for now.
33
34(setf *enable-package-system* nil)
35
8e198a08
MB
36;;; The next command starts the webserver on the port 8000.
37
38(start :port 8000)
39
40;;; We are now ready to generate the first JavaScript-enabled webpages
41;;; using ParenScript.
42
43;;;# A simple embedded example
44
45;;; The first document we will generate is a simple HTML document,
46;;; which features a single hyperlink. When clicking the hyperlink, a
47;;; JavaScript handler opens a popup alert window with the string
48;;; "Hello world". To facilitate the development, we will factor out
49;;; the HTML generation to a separate function, and setup a handler
50;;; for the url "/tutorial1", which will generate HTTP headers and
51;;; call the function `TUTORIAL1'. At first, our function does nothing.
52
53(defun tutorial1 (req ent)
54 (declare (ignore req ent))
55 nil)
56
57(publish :path "/tutorial1"
58 :content-type "text/html; charset=ISO-8859-1"
59 :function #'(lambda (req ent)
60 (with-http-response (req ent)
61 (with-http-body (req ent)
62 (tutorial1 req ent)))))
63
64;;; Browsing "http://localhost:8000/tutorial1" should return an empty
65;;; HTML page. It's now time to fill this rather page with
66;;; content. ParenScript features a macro that generates a string that
94a05cdf 67;;; can be used as an attribute value of HTML nodes.
8e198a08
MB
68
69(defun tutorial1 (req ent)
70 (declare (ignore req ent))
71 (html
72 (:html
73 (:head (:title "ParenScript tutorial: 1st example"))
74 (:body (:h1 "ParenScript tutorial: 1st example")
75 (:p "Please click the link below." :br
76 ((:a :href "#" :onclick (js-inline
77 (alert "Hello World")))
78 "Hello World"))))))
79
80;;; Browsing "http://localhost:8000/tutorial1" should return the
81;;; following HTML:
82
8e198a08
MB
83<html><head><title>ParenScript tutorial: 1st example</title>
84</head>
85<body><h1>ParenScript tutorial: 1st example</h1>
86<p>Please click the link below.<br/>
87<a href="#"
88 onclick="javascript:alert(&quot;Hello World&quot;);">Hello World</a>
89</p>
90</body>
91</html>
92
93;;;# Adding an inline ParenScript
94
95;;; Suppose we now want to have a general greeting function. One way
96;;; to do this is to add the javascript in a `SCRIPT' element at the
97;;; top of the HTML page. This is done using the `JS-SCRIPT' macro
98;;; which will generate the necessary XML and comment tricks to
99;;; cleanly embed JavaScript. We will redefine our `TUTORIAL1'
100;;; function and add a few links:
101
102(defun tutorial1 (req ent)
103 (declare (ignore req ent))
104 (html
105 (:html
106 (:head
107 (:title "ParenScript tutorial: 2nd example")
108 (js-script
109 (defun greeting-callback ()
110 (alert "Hello World"))))
111 (:body
112 (:h1 "ParenScript tutorial: 2nd example")
113 (:p "Please click the link below." :br
114 ((:a :href "#" :onclick (js-inline (greeting-callback)))
115 "Hello World")
116 :br "And maybe this link too." :br
117 ((:a :href "#" :onclick (js-inline (greeting-callback)))
118 "Knock knock")
119 :br "And finally a third link." :br
120 ((:a :href "#" :onclick (js-inline (greeting-callback)))
121 "Hello there"))))))
122
123;;; This will generate the following HTML page, with the embedded
124;;; JavaScript nicely sitting on top. Take note how
125;;; `GREETING-CALLBACK' was converted to camelcase, and how the lispy
126;;; `DEFUN' was converted to a JavaScript function declaration.
127
8e198a08
MB
128<html><head><title>ParenScript tutorial: 2nd example</title>
129<script type="text/javascript">
130// <![CDATA[
131function greetingCallback() {
132 alert("Hello World");
133}
134// ]]>
135</script>
136</head>
137<body><h1>ParenScript tutorial: 2nd example</h1>
138<p>Please click the link below.<br/>
139<a href="#"
140 onclick="javascript:greetingCallback();">Hello World</a>
141<br/>
142And maybe this link too.<br/>
143<a href="#"
144 onclick="javascript:greetingCallback();">Knock knock</a>
145<br/>
146
147And finally a third link.<br/>
148<a href="#"
149 onclick="javascript:greetingCallback();">Hello there</a>
150</p>
151</body>
152</html>
153
154;;;# Generating a JavaScript file
155
156;;; The best way to integrate ParenScript into a Lisp application is
157;;; to generate a JavaScript file from ParenScript code. This file can
158;;; be cached by intermediate proxies, and webbrowsers won't have to
159;;; reload the javascript code on each pageview. A standalone
160;;; JavaScript can be generated using the macro `JS-FILE'. We will
161;;; publish the tutorial JavaScript under "/tutorial.js".
162
163(defun tutorial1-file (req ent)
164 (declare (ignore req ent))
165 (js-file
166 (defun greeting-callback ()
167 (alert "Hello World"))))
168
169(publish :path "/tutorial1.js"
170 :content-type "text/javascript; charset=ISO-8859-1"
171 :function #'(lambda (req ent)
172 (with-http-response (req ent)
173 (with-http-body (req ent)
174 (tutorial1-file req ent)))))
175
176(defun tutorial1 (req ent)
177 (declare (ignore req ent))
178 (html
179 (:html
180 (:head
181 (:title "ParenScript tutorial: 3rd example")
182 ((:script :language "JavaScript" :src "/tutorial1.js")))
183 (:body
184 (:h1 "ParenScript tutorial: 3rd example")
185 (:p "Please click the link below." :br
186 ((:a :href "#" :onclick (js-inline (greeting-callback)))
187 "Hello World")
188 :br "And maybe this link too." :br
189 ((:a :href "#" :onclick (js-inline (greeting-callback)))
190 "Knock knock")
191 :br "And finally a third link." :br
192 ((:a :href "#" :onclick (js-inline (greeting-callback)))
193 "Hello there"))))))
194
195;;; This will generate the following JavaScript code under
196;;; "/tutorial1.js":
197
198function greetingCallback() {
199 alert("Hello World");
200}
201
202;;; and the following HTML code:
203
204<html><head><title>ParenScript tutorial: 3rd example</title>
205<script language="JavaScript" src="/tutorial1.js"></script>
206</head>
207<body><h1>ParenScript tutorial: 3rd example</h1>
208<p>Please click the link below.<br/>
209<a href="#" onclick="javascript:greetingCallback();">Hello World</a>
210<br/>
211And maybe this link too.<br/>
212<a href="#" onclick="javascript:greetingCallback();">Knock knock</a>
213<br/>
214
215And finally a third link.<br/>
216<a href="#" onclick="javascript:greetingCallback();">Hello there</a>
217</p>
218</body>
219</html>
220
221;;;# A ParenScript slideshow
222
223;;; While developing ParenScript, I used JavaScript programs from the
224;;; web and rewrote them using ParenScript. This is a nice slideshow
225;;; example from
226
227 http://www.dynamicdrive.com/dynamicindex14/dhtmlslide.htm
228
229;;; The slideshow will be accessible under "/slideshow", and will
230;;; slide through the images "photo1.png", "photo2.png" and
231;;; "photo3.png". The first ParenScript version will be very similar
232;;; to the original JavaScript code. The second version will then show
233;;; how to integrate data from the Lisp environment into the
234;;; ParenScript code, allowing us to customize the slideshow
235;;; application by supplying a list of image names. We first setup the
236;;; slideshow path.
237
238(publish :path "/slideshow"
239 :content-type "text/html"
240 :function #'(lambda (req ent)
241 (with-http-response (req ent)
242 (with-http-body (req ent)
243 (slideshow req ent)))))
244
245(publish :path "/slideshow.js"
246 :content-type "text/html"
247 :function #'(lambda (req ent)
248 (with-http-response (req ent)
249 (with-http-body (req ent)
250 (js-slideshow req ent)))))
251
252;;; The images are just random images I found on my harddrive. We will
253;;; publish them by hand for now.
254
255(publish-file :path "/photo1.png"
256 :file "/home/manuel/bknr-sputnik.png")
257(publish-file :path "/photo2.png"
258 :file "/home/manuel/bknrlogo_red648.png")
259(publish-file :path "/photo3.png"
260 :file "/home/manuel/bknr-sputnik.png")
261
262;;; The function `SLIDESHOW' generates the HTML code for the main
263;;; slideshow page. It also features little bits of ParenScript. These
264;;; are the callbacks on the links for the slideshow application. In
265;;; this special case, the javascript generates the links itself by
266;;; using `document.write' in a "SCRIPT" element. Users that don't
267;;; have JavaScript enabled won't see anything at all.
268;;;
269;;; `SLIDESHOW' also generates a static array called `PHOTOS' which
270;;; holds the links to the photos of the slideshow. This array is
271;;; handled by the ParenScript code in "slideshow.js". Note how the
272;;; HTML code issued by the JavaScript is generated using the `HTML'
273;;; construct. In fact, we have two different HTML generators in the
274;;; example below, one is the standard Lisp HTML generator, and the
275;;; other is the JavaScript HTML generator, which generates a
276;;; JavaScript expression.
277
278(defun slideshow (req ent)
279 (declare (ignore req ent))
280 (html
281 (:html
282 (:head (:title "ParenScript slideshow")
283 ((:script :language "JavaScript"
284 :src "/slideshow.js"))
285 (js-script
286 (defvar *linkornot* 0)
287 (defvar photos (array "photo1.png"
288 "photo2.png"
289 "photo3.png"))))
290 (:body (:h1 "ParenScript slideshow")
291 (:body (:h2 "Hello")
292 ((:table :border 0
293 :cellspacing 0
294 :cellpadding 0)
295 (:tr ((:td :width "100%" :colspan 2 :height 22)
296 (:center
297 (js-script
298 (let ((img
299 (html
300 ((:img :src (aref photos 0)
301 :name "photoslider"
302 :style ( + "filter:"
303 (js (reveal-trans
304 (setf duration 2)
305 (setf transition 23))))
306 :border 0)))))
307 (document.write
308 (if (= *linkornot* 1)
309 (html ((:a :href "#"
310 :onclick (js-inline (transport)))
311 img))
312 img)))))))
313 (:tr ((:td :width "50%" :height "21")
314 ((:p :align "left")
315 ((:a :href "#"
316 :onclick (js-inline (backward)
317 (return false)))
318 "Previous Slide")))
319 ((:td :width "50%" :height "21")
320 ((:p :align "right")
321 ((:a :href "#"
322 :onclick (js-inline (forward)
323 (return false)))
324 "Next Slide"))))))))))
325
326;;; `SLIDESHOW' generates the following HTML code (long lines have
327;;; been broken down):
328
329<html><head><title>ParenScript slideshow</title>
330<script language="JavaScript" src="/slideshow.js"></script>
331<script type="text/javascript">
332// <![CDATA[
333var LINKORNOT = 0;
334var photos = [ "photo1.png", "photo2.png", "photo3.png" ];
335// ]]>
336</script>
337</head>
338<body><h1>ParenScript slideshow</h1>
339<body><h2>Hello</h2>
340<table border="0" cellspacing="0" cellpadding="0">
341<tr><td width="100%" colspan="2" height="22">
342<center><script type="text/javascript">
343// <![CDATA[
344var img =
345 "<img src=\"" + photos[0]
346 + "\" name=\"photoslider\"
347 style=\"filter:revealTrans(duration=2,transition=23)\"
348 border=\"0\"></img>";
349document.write(LINKORNOT == 1 ?
350 "<a href=\"#\"
351 onclick=\"javascript:transport()\">"
352 + img + "</a>"
353 : img);
354// ]]>
355</script>
356</center>
357</td>
358</tr>
359<tr><td width="50%" height="21"><p align="left">
360<a href="#"
361 onclick="javascript:backward(); return false;">Previous Slide</a>
362
363</p>
364</td>
365<td width="50%" height="21"><p align="right">
366<a href="#"
367 onclick="javascript:forward(); return false;">Next Slide</a>
368</p>
369</td>
370</tr>
371</table>
372</body>
373</body>
374</html>
375
8e198a08
MB
376;;; The actual slideshow application is generated by the function
377;;; `JS-SLIDESHOW', which generates a ParenScript file. The code is
378;;; pretty straightforward for a lisp savy person. Symbols are
379;;; converted to JavaScript variables, but the dot "." is left as
380;;; is. This enables us to access object "slots" without using the
381;;; `SLOT-VALUE' function all the time. However, when the object we
382;;; are referring to is not a variable, but for example an element of
383;;; an array, we have to revert to `SLOT-VALUE'.
384
385(defun js-slideshow (req ent)
386 (declare (ignore req ent))
387 (js-file
388 (defvar *preloaded-images* (make-array))
389 (defun preload-images (photos)
390 (dotimes (i photos.length)
391 (setf (aref *preloaded-images* i) (new *Image)
392 (slot-value (aref *preloaded-images* i) 'src)
393 (aref photos i))))
551080b7 394
8e198a08
MB
395 (defun apply-effect ()
396 (when (and document.all photoslider.filters)
397 (let ((trans photoslider.filters.reveal-trans))
398 (setf (slot-value trans '*Transition)
399 (floor (* (random) 23)))
400 (trans.stop)
401 (trans.apply))))
551080b7 402
8e198a08
MB
403 (defun play-effect ()
404 (when (and document.all photoslider.filters)
405 (photoslider.filters.reveal-trans.play)))
406
407 (defvar *which* 0)
551080b7 408
8e198a08
MB
409 (defun keep-track ()
410 (setf window.status
411 (+ "Image " (1+ *which*) " of " photos.length)))
551080b7 412
8e198a08
MB
413 (defun backward ()
414 (when (> *which* 0)
415 (decf *which*)
416 (apply-effect)
417 (setf document.images.photoslider.src
418 (aref photos *which*))
419 (play-effect)
420 (keep-track)))
551080b7 421
8e198a08
MB
422 (defun forward ()
423 (when (< *which* (1- photos.length))
424 (incf *which*)
425 (apply-effect)
426 (setf document.images.photoslider.src
427 (aref photos *which*))
428 (play-effect)
429 (keep-track)))
551080b7 430
8e198a08
MB
431 (defun transport ()
432 (setf window.location (aref photoslink *which*)))))
433
434;;; `JS-SLIDESHOW' generates the following JavaScript code:
435
436var PRELOADEDIMAGES = new Array();
437function preloadImages(photos) {
438 for (var i = 0; i != photos.length; i = i++) {
439 PRELOADEDIMAGES[i] = new Image;
440 PRELOADEDIMAGES[i].src = photos[i];
441 }
442}
443function applyEffect() {
444 if (document.all && photoslider.filters) {
445 var trans = photoslider.filters.revealTrans;
446 trans.Transition = Math.floor(Math.random() * 23);
447 trans.stop();
448 trans.apply();
449 }
450}
451function playEffect() {
452 if (document.all && photoslider.filters) {
453 photoslider.filters.revealTrans.play();
454 }
455}
456var WHICH = 0;
457function keepTrack() {
458 window.status = "Image " + (WHICH + 1) + " of " +
459 photos.length;
460}
461function backward() {
462 if (WHICH > 0) {
463 --WHICH;
464 applyEffect();
465 document.images.photoslider.src = photos[WHICH];
466 playEffect();
467 keepTrack();
468 }
469}
470function forward() {
471 if (WHICH < photos.length - 1) {
472 ++WHICH;
473 applyEffect();
474 document.images.photoslider.src = photos[WHICH];
475 playEffect();
476 keepTrack();
477 }
478}
479function transport() {
480 window.location = photoslink[WHICH];
481}
482
483;;;# Customizing the slideshow
484
485;;; For now, the slideshow has the path to all the slideshow images
486;;; hardcoded in the HTML code, as well as in the publish
487;;; statements. We now want to customize this by publishing a
488;;; slideshow under a certain path, and giving it a list of image urls
489;;; and pathnames where those images can be found. For this, we will
490;;; create a function `PUBLISH-SLIDESHOW' which takes a prefix as
491;;; argument, as well as a list of image pathnames to be published.
492
493(defun publish-slideshow (prefix images)
494 (let* ((js-url (format nil "~Aslideshow.js" prefix))
495 (html-url (format nil "~Aslideshow" prefix))
496 (image-urls
497 (mapcar #'(lambda (image)
498 (format nil "~A~A.~A" prefix
499 (pathname-name image)
500 (pathname-type image)))
501 images)))
502 (publish :path html-url
503 :content-type "text/html"
504 :function #'(lambda (req ent)
505 (with-http-response (req ent)
506 (with-http-body (req ent)
507 (slideshow2 req ent image-urls)))))
508 (publish :path js-url
509 :content-type "text/html"
510 :function #'(lambda (req ent)
511 (with-http-response (req ent)
512 (with-http-body (req ent)
513 (js-slideshow req ent)))))
514 (map nil #'(lambda (image url)
515 (publish-file :path url
516 :file image))
517 images image-urls)))
518
519(defun slideshow2 (req ent image-urls)
520 (declare (ignore req ent))
521 (html
522 (:html
523 (:head (:title "ParenScript slideshow")
524 ((:script :language "JavaScript"
525 :src "/slideshow.js"))
526 ((:script :type "text/javascript")
527 (:princ (format nil "~%// <![CDATA[~%"))
528 (:princ (js (defvar *linkornot* 0)))
529 (:princ (js-to-string `(defvar photos
530 (array ,@image-urls))))
531 (:princ (format nil "~%// ]]>~%"))))
532 (:body (:h1 "ParenScript slideshow")
533 (:body (:h2 "Hello")
534 ((:table :border 0
535 :cellspacing 0
536 :cellpadding 0)
537 (:tr ((:td :width "100%" :colspan 2 :height 22)
538 (:center
539 (js-script
540 (let ((img
541 (html
542 ((:img :src (aref photos 0)
543 :name "photoslider"
544 :style ( + "filter:"
545 (js (reveal-trans
546 (setf duration 2)
547 (setf transition 23))))
548 :border 0)))))
549 (document.write
550 (if (= *linkornot* 1)
551 (html ((:a :href "#"
552 :onclick (js-inline (transport)))
553 img))
554 img)))))))
555 (:tr ((:td :width "50%" :height "21")
556 ((:p :align "left")
557 ((:a :href "#"
558 :onclick (js-inline (backward)
559 (return false)))
560 "Previous Slide")))
561 ((:td :width "50%" :height "21")
562 ((:p :align "right")
563 ((:a :href "#"
564 :onclick (js-inline (forward)
565 (return false)))
566 "Next Slide"))))))))))
567
568;;; We can now publish the same slideshow as before, under the
569;;; "/bknr/" prefix:
570
94a05cdf 571(publish-slideshow "/bknr/"
8e198a08
MB
572 `("/home/manuel/bknr-sputnik.png"
573 "/home/manuel/bknrlogo_red648.png"
574 "/home/manuel/screenshots/screenshot-14.03.2005-11.54.33.png"))
575
576;;; That's it, we can now access our customized slideshow under
577
578 http://localhost:8000/bknr/slideshow
579