Refine example
svn: r12588
This commit is contained in:
parent
4f482b9f81
commit
ba54379202
|
@ -286,56 +286,107 @@ the template to be unescaped, then create a @scheme[cdata] structure:
|
|||
|
||||
@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:
|
||||
@schememod[
|
||||
scheme
|
||||
(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))
|
||||
The data-structures he uses are defined as:
|
||||
@schemeblock[
|
||||
(define-struct post (title body))
|
||||
|
||||
(define posts
|
||||
(list
|
||||
(make-post
|
||||
"(Y Y) Works: The Why of Y"
|
||||
"..."
|
||||
(list
|
||||
"First post! - A.T."
|
||||
"Didn't I write this? - Matthias"))
|
||||
"Why is Y, that is the question.")
|
||||
(make-post
|
||||
"Church and the States"
|
||||
"As you may know, I grew up in DC, not technically a state..."
|
||||
(list
|
||||
"Finally, A Diet That Really Works! As Seen On TV"))))
|
||||
"As you may know, I grew up in DC, not technically a state.")))
|
||||
]
|
||||
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)
|
||||
`(html
|
||||
(head (title "Alonzo'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 }")))
|
||||
(head (title "Al's Church: " ,section))
|
||||
(body
|
||||
(script ([type "text/javascript"])
|
||||
(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)
|
||||
(h1 "Al's Church: " ,section)
|
||||
(div ([id "main"])
|
||||
(code:comment "He had to be careful to use splicing here")
|
||||
,@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)
|
||||
`((h2 ,title)
|
||||
(p ,body)
|
||||
(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 binds
|
||||
(request-bindings req))
|
||||
|
@ -344,30 +395,13 @@ Here's the code he starts off with:
|
|||
(define body
|
||||
(extract-binding/single 'body binds))
|
||||
(set! posts
|
||||
(list* (make-post title body empty)
|
||||
(list* (make-post title body)
|
||||
posts))
|
||||
(send/suspend
|
||||
(lambda (k-url)
|
||||
(template "Posted" (blog-posted title body k-url))))
|
||||
(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)
|
||||
(extract-post
|
||||
(send/suspend
|
||||
|
@ -376,19 +410,29 @@ Here's the code he starts off with:
|
|||
|
||||
(define (start req)
|
||||
(display-posts))
|
||||
|
||||
(serve/servlet start)
|
||||
]
|
||||
|
||||
Luckily, Alonzo has great software engineering skills, so he's already separated the presentation logic into the functions
|
||||
@scheme[blog-posted], @scheme[blog-posts], and @scheme[template]. Each one of these will turn into a different
|
||||
template.
|
||||
To use templates, we need only change @scheme[template], @scheme[blog-posted], and @scheme[blog-posts]:
|
||||
|
||||
@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}:
|
||||
@verbatim[#:indent 2]|{
|
||||
<html>
|
||||
<head>
|
||||
<title>Alonzo's Church: @|section|</title>
|
||||
<title>Al's Church: @|section|</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0px;
|
||||
|
@ -413,7 +457,7 @@ template.
|
|||
pageTracker._trackPageview();
|
||||
</script>
|
||||
|
||||
<h1>Alonzo's Church: @|section|</h1>
|
||||
<h1>Al's Church: @|section|</h1>
|
||||
<div id="main">
|
||||
@body
|
||||
</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
|
||||
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}:
|
||||
@verbatim[#:indent 2]|{
|
||||
@in[p posts]{
|
||||
<h2>@(post-title p)</h2>
|
||||
<p>@(post-body p)</p>
|
||||
<ul>
|
||||
@in[c (post-comments p)]{
|
||||
<li>@|c|</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
<h1>New Post</h1>
|
||||
|
@ -446,55 +493,5 @@ remember if splicing is necessary.
|
|||
</form>
|
||||
}|
|
||||
|
||||
This template is even simpler, because there is no list management whatsoever. The defaults "just work".
|
||||
For completeness, we show the final template:
|
||||
|
||||
@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)
|
||||
]
|
||||
Compare this template with the original presentation function: there is no need to worry about managing how lists
|
||||
are nested: the defaults @emph{just work}.
|
||||
|
|
Loading…
Reference in New Issue
Block a user