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 | |
274 | ;;; HTML code issued by the JavaScript is generated using the `HTML' | |
275 | ;;; construct. In fact, we have two different HTML generators in the | |
276 | ;;; example below, one is the standard Lisp HTML generator, and the | |
277 | ;;; other is the JavaScript HTML generator, which generates a | |
278 | ;;; JavaScript expression. | |
279 | ||
280 | (defun slideshow (req ent) | |
281 | (declare (ignore req ent)) | |
282 | (html | |
283 | (:html | |
284 | (:head (:title "ParenScript slideshow") | |
285 | ((:script :language "JavaScript" | |
286 | :src "/slideshow.js")) | |
287 | (js-script | |
288 | (defvar *linkornot* 0) | |
6291b505 VS |
289 | (defvar photos (array "photo1.jpg" |
290 | "photo2.jpg" | |
291 | "photo3.jpg")))) | |
8e198a08 MB |
292 | (:body (:h1 "ParenScript slideshow") |
293 | (:body (:h2 "Hello") | |
294 | ((:table :border 0 | |
295 | :cellspacing 0 | |
296 | :cellpadding 0) | |
297 | (:tr ((:td :width "100%" :colspan 2 :height 22) | |
298 | (:center | |
299 | (js-script | |
6291b505 VS |
300 | (let ((img (ps-html |
301 | ((:img :src (aref photos 0) | |
302 | :name "photoslider" | |
303 | :style (+ "filter:" | |
304 | (lisp (ps (reveal-trans | |
305 | (setf duration 2) | |
306 | (setf transition 23))))) | |
307 | :border 0))))) | |
8e198a08 MB |
308 | (document.write |
309 | (if (= *linkornot* 1) | |
6291b505 VS |
310 | (ps-html ((:a :href "#" |
311 | :onclick (lisp (ps-inline (transport)))) | |
312 | img)) | |
8e198a08 MB |
313 | img))))))) |
314 | (:tr ((:td :width "50%" :height "21") | |
315 | ((:p :align "left") | |
316 | ((:a :href "#" | |
6291b505 | 317 | :onclick (ps-inline (backward) |
8e198a08 MB |
318 | (return false))) |
319 | "Previous Slide"))) | |
320 | ((:td :width "50%" :height "21") | |
321 | ((:p :align "right") | |
322 | ((:a :href "#" | |
6291b505 | 323 | :onclick (ps-inline (forward) |
8e198a08 MB |
324 | (return false))) |
325 | "Next Slide")))))))))) | |
326 | ||
327 | ;;; `SLIDESHOW' generates the following HTML code (long lines have | |
328 | ;;; been broken down): | |
329 | ||
330 | <html><head><title>ParenScript slideshow</title> | |
331 | <script language="JavaScript" src="/slideshow.js"></script> | |
332 | <script type="text/javascript"> | |
333 | // <![CDATA[ | |
334 | var LINKORNOT = 0; | |
6291b505 | 335 | var photos = [ "photo1.jpg", "photo2.jpg", "photo3.jpg" ]; |
8e198a08 MB |
336 | // ]]> |
337 | </script> | |
338 | </head> | |
339 | <body><h1>ParenScript slideshow</h1> | |
340 | <body><h2>Hello</h2> | |
341 | <table border="0" cellspacing="0" cellpadding="0"> | |
342 | <tr><td width="100%" colspan="2" height="22"> | |
343 | <center><script type="text/javascript"> | |
344 | // <![CDATA[ | |
345 | var img = | |
346 | "<img src=\"" + photos[0] | |
347 | + "\" name=\"photoslider\" | |
348 | style=\"filter:revealTrans(duration=2,transition=23)\" | |
349 | border=\"0\"></img>"; | |
350 | document.write(LINKORNOT == 1 ? | |
351 | "<a href=\"#\" | |
352 | onclick=\"javascript:transport()\">" | |
353 | + img + "</a>" | |
354 | : img); | |
355 | // ]]> | |
356 | </script> | |
357 | </center> | |
358 | </td> | |
359 | </tr> | |
360 | <tr><td width="50%" height="21"><p align="left"> | |
361 | <a href="#" | |
362 | onclick="javascript:backward(); return false;">Previous Slide</a> | |
363 | ||
364 | </p> | |
365 | </td> | |
366 | <td width="50%" height="21"><p align="right"> | |
367 | <a href="#" | |
368 | onclick="javascript:forward(); return false;">Next Slide</a> | |
369 | </p> | |
370 | </td> | |
371 | </tr> | |
372 | </table> | |
373 | </body> | |
374 | </body> | |
375 | </html> | |
376 | ||
8e198a08 | 377 | ;;; The actual slideshow application is generated by the function |
6291b505 | 378 | ;;; `JS-SLIDESHOW', which generates a ParenScript file. Symbols are |
8e198a08 | 379 | ;;; converted to JavaScript variables, but the dot "." is left as |
6291b505 VS |
380 | ;;; is. This enables convenient access to object slots without using |
381 | ;;; the `SLOT-VALUE' function all the time. However, when the object | |
382 | ;;; we are referring to is not a variable, but for example an element | |
383 | ;;; of an array, we have to revert to `SLOT-VALUE'. | |
8e198a08 MB |
384 | |
385 | (defun js-slideshow (req ent) | |
386 | (declare (ignore req ent)) | |
6291b505 VS |
387 | (html |
388 | (:princ | |
389 | (ps | |
390 | (defvar *preloaded-images* (make-array)) | |
391 | (defun preload-images (photos) | |
392 | (dotimes (i photos.length) | |
393 | (setf (aref *preloaded-images* i) (new *Image) | |
394 | (slot-value (aref *preloaded-images* i) 'src) | |
395 | (aref photos i)))) | |
396 | ||
397 | (defun apply-effect () | |
398 | (when (and document.all photoslider.filters) | |
399 | (let ((trans photoslider.filters.reveal-trans)) | |
400 | (setf (slot-value trans '*Transition) | |
401 | (floor (* (random) 23))) | |
402 | (trans.stop) | |
403 | (trans.apply)))) | |
404 | ||
405 | (defun play-effect () | |
406 | (when (and document.all photoslider.filters) | |
407 | (photoslider.filters.reveal-trans.play))) | |
408 | ||
409 | (defvar *which* 0) | |
410 | ||
411 | (defun keep-track () | |
412 | (setf window.status | |
413 | (+ "Image " (1+ *which*) " of " photos.length))) | |
414 | ||
415 | (defun backward () | |
416 | (when (> *which* 0) | |
417 | (decf *which*) | |
418 | (apply-effect) | |
419 | (setf document.images.photoslider.src | |
420 | (aref photos *which*)) | |
421 | (play-effect) | |
422 | (keep-track))) | |
423 | ||
424 | (defun forward () | |
425 | (when (< *which* (1- photos.length)) | |
426 | (incf *which*) | |
427 | (apply-effect) | |
428 | (setf document.images.photoslider.src | |
429 | (aref photos *which*)) | |
430 | (play-effect) | |
431 | (keep-track))) | |
432 | ||
433 | (defun transport () | |
434 | (setf window.location (aref photoslink *which*))))))) | |
8e198a08 MB |
435 | |
436 | ;;; `JS-SLIDESHOW' generates the following JavaScript code: | |
437 | ||
438 | var PRELOADEDIMAGES = new Array(); | |
439 | function preloadImages(photos) { | |
440 | for (var i = 0; i != photos.length; i = i++) { | |
441 | PRELOADEDIMAGES[i] = new Image; | |
442 | PRELOADEDIMAGES[i].src = photos[i]; | |
443 | } | |
444 | } | |
445 | function applyEffect() { | |
446 | if (document.all && photoslider.filters) { | |
447 | var trans = photoslider.filters.revealTrans; | |
448 | trans.Transition = Math.floor(Math.random() * 23); | |
449 | trans.stop(); | |
450 | trans.apply(); | |
451 | } | |
452 | } | |
453 | function playEffect() { | |
454 | if (document.all && photoslider.filters) { | |
455 | photoslider.filters.revealTrans.play(); | |
456 | } | |
457 | } | |
458 | var WHICH = 0; | |
459 | function keepTrack() { | |
460 | window.status = "Image " + (WHICH + 1) + " of " + | |
461 | photos.length; | |
462 | } | |
463 | function backward() { | |
464 | if (WHICH > 0) { | |
465 | --WHICH; | |
466 | applyEffect(); | |
467 | document.images.photoslider.src = photos[WHICH]; | |
468 | playEffect(); | |
469 | keepTrack(); | |
470 | } | |
471 | } | |
472 | function forward() { | |
473 | if (WHICH < photos.length - 1) { | |
474 | ++WHICH; | |
475 | applyEffect(); | |
476 | document.images.photoslider.src = photos[WHICH]; | |
477 | playEffect(); | |
478 | keepTrack(); | |
479 | } | |
480 | } | |
481 | function transport() { | |
482 | window.location = photoslink[WHICH]; | |
483 | } | |
484 | ||
485 | ;;;# Customizing the slideshow | |
486 | ||
487 | ;;; For now, the slideshow has the path to all the slideshow images | |
488 | ;;; hardcoded in the HTML code, as well as in the publish | |
489 | ;;; statements. We now want to customize this by publishing a | |
490 | ;;; slideshow under a certain path, and giving it a list of image urls | |
491 | ;;; and pathnames where those images can be found. For this, we will | |
492 | ;;; create a function `PUBLISH-SLIDESHOW' which takes a prefix as | |
493 | ;;; argument, as well as a list of image pathnames to be published. | |
494 | ||
495 | (defun publish-slideshow (prefix images) | |
496 | (let* ((js-url (format nil "~Aslideshow.js" prefix)) | |
497 | (html-url (format nil "~Aslideshow" prefix)) | |
498 | (image-urls | |
6291b505 VS |
499 | (mapcar (lambda (image) |
500 | (format nil "~A~A.~A" prefix | |
501 | (pathname-name image) | |
502 | (pathname-type image))) | |
8e198a08 MB |
503 | images))) |
504 | (publish :path html-url | |
505 | :content-type "text/html" | |
6291b505 VS |
506 | :function (lambda (req ent) |
507 | (with-http-response (req ent) | |
508 | (with-http-body (req ent) | |
509 | (slideshow2 req ent image-urls))))) | |
8e198a08 MB |
510 | (publish :path js-url |
511 | :content-type "text/html" | |
6291b505 VS |
512 | :function (lambda (req ent) |
513 | (with-http-response (req ent) | |
514 | (with-http-body (req ent) | |
515 | (js-slideshow req ent))))) | |
516 | (map nil (lambda (image url) | |
517 | (publish-file :path url | |
518 | :file image)) | |
8e198a08 MB |
519 | images image-urls))) |
520 | ||
521 | (defun slideshow2 (req ent image-urls) | |
522 | (declare (ignore req ent)) | |
523 | (html | |
524 | (:html | |
525 | (:head (:title "ParenScript slideshow") | |
526 | ((:script :language "JavaScript" | |
527 | :src "/slideshow.js")) | |
528 | ((:script :type "text/javascript") | |
529 | (:princ (format nil "~%// <![CDATA[~%")) | |
6291b505 VS |
530 | (:princ (ps (defvar *linkornot* 0))) |
531 | (:princ (ps* `(defvar photos (array ,@image-urls)))) | |
8e198a08 MB |
532 | (:princ (format nil "~%// ]]>~%")))) |
533 | (:body (:h1 "ParenScript slideshow") | |
534 | (:body (:h2 "Hello") | |
535 | ((:table :border 0 | |
536 | :cellspacing 0 | |
537 | :cellpadding 0) | |
538 | (:tr ((:td :width "100%" :colspan 2 :height 22) | |
539 | (:center | |
540 | (js-script | |
6291b505 VS |
541 | (let ((img (ps-html |
542 | ((:img :src (aref photos 0) | |
543 | :name "photoslider" | |
544 | :style (+ "filter:" | |
545 | (lisp (ps (reveal-trans | |
546 | (setf duration 2) | |
547 | (setf transition 23))))) | |
548 | :border 0))))) | |
8e198a08 MB |
549 | (document.write |
550 | (if (= *linkornot* 1) | |
6291b505 VS |
551 | (ps-html ((:a :href "#" |
552 | :onclick (lisp (ps-inline (transport)))) | |
553 | img)) | |
8e198a08 MB |
554 | img))))))) |
555 | (:tr ((:td :width "50%" :height "21") | |
556 | ((:p :align "left") | |
557 | ((:a :href "#" | |
6291b505 | 558 | :onclick (ps-inline (backward) |
8e198a08 MB |
559 | (return false))) |
560 | "Previous Slide"))) | |
561 | ((:td :width "50%" :height "21") | |
562 | ((:p :align "right") | |
563 | ((:a :href "#" | |
6291b505 | 564 | :onclick (ps-inline (forward) |
8e198a08 MB |
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/" |
6291b505 | 572 | `("/home/viper/photo1.jpg" "/home/viper/photo2.jpg" "/home/viper/photo3.jpg")) |
8e198a08 MB |
573 | |
574 | ;;; That's it, we can now access our customized slideshow under | |
575 | ||
6291b505 | 576 | http://localhost:8080/bknr/slideshow |
8e198a08 | 577 |