7 content=
"width=device-width, initial-scale=1"
10 <title>Parenscript Tutorial
</title>
11 <style type=
"text/css">
17 border: 1px solid
#d5d5d5;
25 <h1 style=
"text-align:center;">Parenscript Tutorial
</h1>
30 This tutorial shows how to build a simple web application in
31 Common Lisp, specifically demonstrating
32 the
<a href=
"http://common-lisp.net/project/parenscript/">Parenscript
</a>
33 Lisp to JavaScript compiler.
37 The
<a href=
"reference.html">Parenscript
38 reference manual
</a> contains a description of Parenscript
42 <h2>Getting Started
</h2>
45 First, install a Common Lisp
46 implementation.
<a href=
"http://sbcl.org/">SBCL
</a> is a good
47 one;
<a href=
"https://www.cliki.net/">CLiki
</a> has
48 a
<a href=
"https://www.cliki.net/Common+Lisp+implementation">comprehensive
49 list of Common Lisp implementations
</a>. Next, get
50 the
<a href=
"https://www.quicklisp.org/">Quicklisp
</a> package
55 This tutorial uses the following libraries:
59 <dt><a href=
"https://edicl.github.io/cl-fad/">CL-FAD
</a></dt>
60 <dd>file utilities
</dd>
62 <dt><a href=
"https://edicl.github.io/cl-who/">CL-WHO
</a></dt>
63 <dd>HTML generator
</dd>
65 <dt><a href=
"https://edicl.github.io/hunchentoot/">Hunchentoot
</a></dt>
68 <dt><a href=
"https://common-lisp.net/project/parenscript/">Parenscript
</a></dt>
69 <dd>JavaScript generator
</dd>
73 Load them using Quicklisp:
76 <pre><code>(mapc #'ql:quickload '(:cl-fad :cl-who :hunchentoot :parenscript))
</code></pre>
79 Next, define a package to hold the example code:
82 <pre><code>(defpackage
"PS-TUTORIAL
"
83 (:use
"COMMON-LISP
" "HUNCHENTOOT
" "CL-WHO
" "PARENSCRIPT
" "CL-FAD
"))
85 (in-package
"PS-TUTORIAL
")
</code></pre>
88 CL-WHO leaves it up to you to escape HTML attributes. One way to
89 make sure that quoted strings in inline JavaScript work inside
90 HTML attributes is to use double quotes for HTML attributes and
91 single quotes for JavaScript strings.
94 <pre><code>(setq cl-who:*attribute-quote-char* #\
")
</code></pre>
97 Now start the web server:
100 <pre><code>(start (make-instance 'easy-acceptor :port
8080))
</code></pre>
105 The
<code>ps
</code> macro takes Parenscript code in the form of
106 s-expressions (Parenscript code and Common Lisp code share the
107 same representation), translates as much as it can into constant
108 strings at macro-expansion time, and expands into a form that
109 will evaluate to a string containing JavaScript code.
112 <pre><code>(define-easy-handler (example1 :uri
"/example1") ()
113 (with-html-output-to-string (s)
115 (:head (:title
"Parenscript tutorial: 1st example"))
116 (:body (:h2
"Parenscript tutorial: 1st example")
117 "Please click the link below." :br
118 (:a :href
"#" :onclick (ps (alert
"Hello World"))
119 "Hello World")))))
</code></pre>
122 One way to include Parenscript code in web pages is to inline it
123 in HTML
<code>script
</code> tags:
126 <pre><code>(define-easy-handler (example2 :uri
"/example2") ()
127 (with-html-output-to-string (s)
130 (:title
"Parenscript tutorial: 2nd example")
131 (:script :type
"text/javascript"
133 (defun greeting-callback ()
134 (alert
"Hello World"))))))
136 (:h2
"Parenscript tutorial: 2nd example")
137 (:a :href
"#" :onclick (ps (greeting-callback))
138 "Hello World")))))
</code></pre>
141 Another way to integrate Parenscript into a web application is
142 to serve the generated JavaScript as a separate HTTP resource.
143 Requests to this resource can then be cached by the browser:
146 <pre><code>(define-easy-handler (example3 :uri
"/example3.js") ()
147 (setf (content-type*)
"text/javascript")
149 (defun greeting-callback ()
150 (alert
"Hello World"))))
</code></pre>
155 Next let's try a more complicated example: an image slideshow
160 First we need a way to define slideshows. For this tutorial we
161 will assume that we have several different folders containing
162 image files, and we want to serve each of the folders as its own
163 slideshow under its own URL. We will use a custom Hunchentoot
164 handler to serve the slideshow
165 under
<samp>/slideshows/{slideshow-name}
</samp>, and the
167 <a href=
"https://edicl.github.io/hunchentoot/#create-folder-dispatcher-and-handler">folder
168 dispatcher
</a> to serve the image files
169 from
<samp>/slideshow-images/{slideshow-name}/{image-file}
</samp>.
172 <pre><code>(defvar *slideshows* (make-hash-table :test 'equalp))
174 (defun add-slideshow (slideshow-name image-folder)
175 (setf (gethash slideshow-name *slideshows*) image-folder)
176 (push (create-folder-dispatcher-and-handler
177 (format nil
"/slideshow-images/~a/" slideshow-name)
179 *dispatch-table*))
</code></pre>
182 Let's find some important pictures on our machine and get
183 Hunchentoot to start serving them:
186 <pre><code>(add-slideshow
"lolcat" "/home/junk/lolcats/")
187 (add-slideshow
"lolrus" "/home/other-junk/lolruses/")
</code></pre>
190 Next we need to create the slideshow web page. We can use
191 JavaScript to view the slideshow without refreshing the whole
192 page, and provide regular link navigation for client browsers
193 that do not have JavaScript enabled. Either way, we want viewers
194 of our slideshow to be able to bookmark their place in the
195 slideshow viewing sequence.
199 We will need a way to generate URIs for slideshow images on both
200 the server and browser. We can eliminate code duplication with
201 the
<code>defmacro+ps
</code> macro, which shares macro
202 definitions between Common Lisp and Parenscript.
205 <pre><code>(defmacro+ps slideshow-image-uri (slideshow-name image-file)
206 `(concatenate 'string
"/slideshow-images/" ,slideshow-name
"/" ,image-file))
</code></pre>
209 Next is the function to serve up the slideshow page. The pages
210 will be served under
<samp>/slideshows/{slideshow-name}
</samp>,
211 all of them handled by a single function that will dispatch on
212 <samp>{slideshow-name}
</samp>.
216 JavaScript-enabled web browsers will get information about the
217 slideshow in an inline script generated
218 by
<a href=
"reference.html#ps*"><code>ps*
</code></a>,
219 a function used for translating code generated at run-time.
220 Slideshow navigation will be done with
<code>onclick
</code>
221 handlers, generated at compile-time by
222 the
<a href=
"reference.html#ps"><code>ps
</code></a>
227 Regular HTML slideshow navigation will be done using query
231 <pre><code>(defun slideshow-handler ()
232 (cl-ppcre:register-groups-bind (slideshow-name)
233 (
"/slideshows/(.*)" (script-name*))
234 (let* ((images (mapcar
235 (lambda (i) (url-encode (file-namestring i)))
237 (or (gethash slideshow-name *slideshows*)
238 (progn (setf (return-code*)
404)
239 (return-from slideshow-handler))))))
241 (or (position (url-encode (or (get-parameter
"image")
""))
245 (previous-image-index (max
0
246 (
1- current-image-index)))
247 (next-image-index (min (
1- (length images))
248 (
1+ current-image-index))))
249 (with-html-output-to-string (s)
252 (:title
"Parenscript slideshow")
254 :type
"text/javascript"
258 (var *slideshow-name* ,slideshow-name)
259 (var *images* (array ,@images))
260 (var *current-image-index* ,current-image-index)))))
261 (:script :type
"text/javascript" :src
"/slideshow.js"))
263 (:div :id
"slideshow-container"
264 :style
"width:100%;text-align:center"
265 (:img :id
"slideshow-img-object"
266 :src (slideshow-image-uri
268 (elt images current-image-index)))
270 (:a :href (format nil
"/slideshows/~a?image=~a"
272 (elt images previous-image-index))
273 :onclick (ps (previous-image) (return false))
276 (:a :href (format nil
"/slideshows/~a?image=~a"
278 (elt images next-image-index))
279 :onclick (ps (next-image) (return false))
280 "Next"))))))))
</code></pre>
283 Since this function is a custom handler, we need to create a new
284 dispatcher for it. Note that we are passing the symbol naming
285 the handler instead of the function object, which lets us
286 redefine the handler without touching the dispatcher.
289 <pre><code>(push (create-prefix-dispatcher
"/slideshows/" 'slideshow-handler)
290 *dispatch-table*)
</code></pre>
293 Last, we need to define the
<samp>/slideshow.js
</samp> script.
296 <pre><code>(define-easy-handler (js-slideshow :uri
"/slideshow.js") ()
297 (setf (content-type*)
"text/javascript")
299 (define-symbol-macro fragment-identifier (@ window location hash))
301 (defun show-image-number (image-index)
302 (let ((image-name (aref *images* (setf *current-image-index* image-index))))
303 (setf (chain document (get-element-by-id
"slideshow-img-object") src)
304 (slideshow-image-uri *slideshow-name* image-name)
308 (defun previous-image ()
309 (when (
> *current-image-index*
0)
310 (show-image-number (
1- *current-image-index*))))
313 (when (< *current-image-index* (
1- (getprop *images* 'length)))
314 (show-image-number (
1+ *current-image-index*))))
316 ;; use fragment identifiers to allow bookmarking
317 (setf (getprop window 'onload)
319 (when fragment-identifier
320 (let ((image-name (chain fragment-identifier (slice
1))))
321 (dotimes (i (length *images*))
322 (when (string= image-name (aref *images* i))
323 (show-image-number i)))))))))
</code></pre>
327 the
<a href=
"reference.html#@"><code>@
</code></a>
328 and
<a href=
"reference.html#chain"><code>chain
</code></a>
329 property access convenience macros.
<code>(@ object slotA
330 slotB)
</code> expands to
331 <code>(getprop (getprop object 'slotA)
332 'slotB)
</code>.
<code>chain
</code> is similar and also provides
336 <p style=
"font-size:xx-small; float:right;">Author: Vladimir Sedach
<<a href=
"mailto:vsedach@oneofus.la">vsedach@oneofus.la
</a>> Last modified:
2018-
03-
29</p>