Refine example

svn: r12588
This commit is contained in:
Jay McCarthy 2008-11-25 16:03:31 +00:00
parent 4f482b9f81
commit ba54379202

View File

@ -286,56 +286,107 @@ the template to be unescaped, then create a @scheme[cdata] structure:
@section{Conversion Example} @section{Conversion Example}
Alonzo Church has been maintaining a blog with PLT Scheme for some years and would like to convert to @schememodname[web-server/templates]. Al Church has been maintaining a blog with PLT Scheme for some years and would like to convert to @schememodname[web-server/templates].
Here's the code he starts off with: The data-structures he uses are defined as:
@schememod[ @schemeblock[
scheme (define-struct post (title body))
(require xml
web-server/servlet
web-server/servlet-env)
(code:comment "He actually Church-encodes them, but we'll use structs.")
(define-struct post (title body comments))
(define posts (define posts
(list (list
(make-post (make-post
"(Y Y) Works: The Why of Y" "(Y Y) Works: The Why of Y"
"..." "Why is Y, that is the question.")
(list
"First post! - A.T."
"Didn't I write this? - Matthias"))
(make-post (make-post
"Church and the States" "Church and the States"
"As you may know, I grew up in DC, not technically a state..." "As you may know, I grew up in DC, not technically a state.")))
(list ]
"Finally, A Diet That Really Works! As Seen On TV")))) Actually, Al Church-encodes these posts, but for explanatory reasons, we'll use structs.
(code:comment "A function that is the generic template for the site") He has divided his code into presentation functions and logic functions. We'll look at the presentation functions first.
The first presentation function defines the common layout of all pages.
@schemeblock[
(define (template section body) (define (template section body)
`(html `(html
(head (title "Alonzo's Church: " ,section) (head (title "Al's Church: " ,section))
(style ([type "text/css"])
(code:comment "CDATA objects were useful for returning raw data")
,(make-cdata #f #f "\n body {\n margin: 0px;\n padding: 10px;\n }\n\n #main {\n background: #dddddd;\n }")))
(body (body
(script ([type "text/javascript"]) (h1 "Al's Church: " ,section)
(code:comment "Which is particularly useful for JavaScript")
,(make-cdata #f #f "\n var gaJsHost = ((\"https:\" == document.location.protocol) ?\n \"https://ssl.\" : \"http://www.\");\n document.write(unescape(\"%3Cscript src='\" + gaJsHost +\n \"google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));\n"))
(script ([type "text/javascript"])
,(make-cdata #f #f "\n var pageTracker = _gat._getTracker(\"UA-YYYYYYY-Y\");\n pageTracker._trackPageview();\n"))
(h1 "Alonzo's Church: " ,section)
(div ([id "main"]) (div ([id "main"])
(code:comment "He had to be careful to use splicing here")
,@body)))) ,@body))))
]
One of the things to notice here is the @scheme[unquote-splicing] on the @scheme[body] argument.
This indicates that the @scheme[body] is list of @|xexpr|s. If he had accidentally used only @scheme[unquote]
then there would be an error in converting the return value to an HTTP response.
@schemeblock[
(define (blog-posted title body k-url) (define (blog-posted title body k-url)
`((h2 ,title) `((h2 ,title)
(p ,body) (p ,body)
(h1 (a ([href ,k-url]) "Continue")))) (h1 (a ([href ,k-url]) "Continue"))))
]
Here's an example of simple body that uses a list of @|xexpr|s to show the newly posted blog entry, before continuing to redisplay
the main page. Let's look at a more complicated body:
@schemeblock[
(define (blog-posts k-url)
(append
(apply append
(for/list ([p posts])
`((h2 ,(post-title p))
(p ,(post-body p)))))
`((h1 "New Post")
(form ([action ,k-url])
(input ([name "title"]))
(input ([name "body"]))
(input ([type "submit"]))))))
]
This function shows a number of common patterns that are required by @|xexpr|s. First, @scheme[append] is used to combine
different @|xexpr| lists. Second, @scheme[apply append] is used to collapse and combine the results of a @scheme[for/list]
where each iteration results in a list of @|xexpr|s. We'll see that these patterns are unnecessary with templates. Another
annoying patterns shows up when Al tries to add CSS styling and some JavaScript from Google Analytics to all the pages of
his blog. He changes the @scheme[template] function to:
@schemeblock[
(define (template section body)
`(html
(head
(title "Al's Church: " ,section)
(style ([type "text/css"])
"body {margin: 0px; padding: 10px;}"
"#main {background: #dddddd;}"))
(body
(script
([type "text/javascript"])
,(make-cdata
#f #f
"var gaJsHost = ((\"https:\" =="
"document.location.protocol)"
"? \"https://ssl.\" : \"http://www.\");"
"document.write(unescape(\"%3Cscript src='\" + gaJsHost"
"+ \"google-analytics.com/ga.js' "
"type='text/javascript'%3E%3C/script%3E\"));"))
(script
([type "text/javascript"])
,(make-cdata
#f #f
"var pageTracker = _gat._getTracker(\"UA-YYYYYYY-Y\");"
"pageTracker._trackPageview();"))
(h1 "Al's Church: " ,section)
(div ([id "main"])
,@body))))
]
The first thing we notice is that encoding CSS as a string is rather primitive. Encoding JavaScript with strings is even worse for two
reasons: first, we are more likely to need to manually escape characters such as @"\""; second, we need to use a CDATA object, because most
JavaScript code uses characters that "need" to be escaped in XML, such as &, but most browsers will fail if these characters are
entity-encoded. These are all problems that go away with templates.
Before moving to templates, let's look at the logic functions:
@schemeblock[
(define (extract-post req) (define (extract-post req)
(define binds (define binds
(request-bindings req)) (request-bindings req))
@ -344,30 +395,13 @@ Here's the code he starts off with:
(define body (define body
(extract-binding/single 'body binds)) (extract-binding/single 'body binds))
(set! posts (set! posts
(list* (make-post title body empty) (list* (make-post title body)
posts)) posts))
(send/suspend (send/suspend
(lambda (k-url) (lambda (k-url)
(template "Posted" (blog-posted title body k-url)))) (template "Posted" (blog-posted title body k-url))))
(display-posts)) (display-posts))
(define (blog-posts k-url)
(code:comment "append or splicing is needed")
(append
(code:comment "Each element of the list is another list")
(apply append
(for/list ([p posts])
`((h2 ,(post-title p))
(p ,(post-body p))
(ul
,@(for/list ([c (post-comments p)])
`(li ,c))))))
`((h1 "New Post")
(form ([action ,k-url])
(input ([name "title"]))
(input ([name "body"]))
(input ([type "submit"]))))))
(define (display-posts) (define (display-posts)
(extract-post (extract-post
(send/suspend (send/suspend
@ -376,19 +410,29 @@ Here's the code he starts off with:
(define (start req) (define (start req)
(display-posts)) (display-posts))
(serve/servlet start)
] ]
Luckily, Alonzo has great software engineering skills, so he's already separated the presentation logic into the functions To use templates, we need only change @scheme[template], @scheme[blog-posted], and @scheme[blog-posts]:
@scheme[blog-posted], @scheme[blog-posts], and @scheme[template]. Each one of these will turn into a different
template. @schemeblock[
(define (template section body)
(list TEXT/HTML-MIME-TYPE
(include-template "blog.html")))
(define (blog-posted title body k-url)
(include-template "blog-posted.html"))
(define (blog-posts k-url)
(include-template "blog-posts.html"))
]
Each of the templates are given below:
@filepath{blog.html}: @filepath{blog.html}:
@verbatim[#:indent 2]|{ @verbatim[#:indent 2]|{
<html> <html>
<head> <head>
<title>Alonzo's Church: @|section|</title> <title>Al's Church: @|section|</title>
<style type="text/css"> <style type="text/css">
body { body {
margin: 0px; margin: 0px;
@ -413,7 +457,7 @@ template.
pageTracker._trackPageview(); pageTracker._trackPageview();
</script> </script>
<h1>Alonzo's Church: @|section|</h1> <h1>Al's Church: @|section|</h1>
<div id="main"> <div id="main">
@body @body
</div> </div>
@ -426,16 +470,19 @@ can be included verbatim, without resorting to any special escape-escaping patte
Similarly, since the @scheme[body] is represented as a string, there is no need to Similarly, since the @scheme[body] is represented as a string, there is no need to
remember if splicing is necessary. remember if splicing is necessary.
@filepath{blog-posted.html}:
@verbatim[#:indent 2]|{
<h2>@|title|</h2>
<p>@|body|</p>
<h1><a href="@|k-url|">Continue</a></h1>
}|
@filepath{blog-posts.html}: @filepath{blog-posts.html}:
@verbatim[#:indent 2]|{ @verbatim[#:indent 2]|{
@in[p posts]{ @in[p posts]{
<h2>@(post-title p)</h2> <h2>@(post-title p)</h2>
<p>@(post-body p)</p> <p>@(post-body p)</p>
<ul>
@in[c (post-comments p)]{
<li>@|c|</li>
}
</ul>
} }
<h1>New Post</h1> <h1>New Post</h1>
@ -446,55 +493,5 @@ remember if splicing is necessary.
</form> </form>
}| }|
This template is even simpler, because there is no list management whatsoever. The defaults "just work". Compare this template with the original presentation function: there is no need to worry about managing how lists
For completeness, we show the final template: are nested: the defaults @emph{just work}.
@filepath{blog-posted.html}:
@verbatim[#:indent 2]|{
<h2>@|title|</h2>
<p>@|body|</p>
<h1><a href="@|k-url|">Continue</a></h1>
}|
The code associated with these templates is very simple as well:
@schememod[
scheme
(require web-server/templates
web-server/servlet
web-server/servlet-env)
(define-struct post (title body comments))
(define posts ....)
(define (template section body)
(list TEXT/HTML-MIME-TYPE
(include-template "blog.html")))
(define (extract-post req)
(define binds
(request-bindings req))
(define title
(extract-binding/single 'title binds))
(define body
(extract-binding/single 'body binds))
(set! posts
(list* (make-post title body empty)
posts))
(send/suspend
(lambda (k-url)
(template "Posted" (include-template "blog-posted.html"))))
(display-posts))
(define (display-posts)
(extract-post
(send/suspend
(lambda (k-url)
(template "Posts" (include-template "blog-posts.html"))))))
(define (start req)
(display-posts))
(serve/servlet start)
]