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