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