Extended example

svn: r12550
This commit is contained in:
Jay McCarthy 2008-11-20 23:57:32 +00:00
parent bea2297421
commit daff0abe15
6 changed files with 475 additions and 12 deletions

View File

@ -0,0 +1,4 @@
<h2>@|title|</h2>
<p>@|body|</p>
<h1><a href="@|k-url|">Continue</a></h1>

View File

@ -0,0 +1,16 @@
@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>
<form action="@|k-url|">
<input name="title" />
<input name="body" />
<input type="submit" />
</form>

View File

@ -0,0 +1,93 @@
#lang scheme
(require web-server/servlet
xml
web-server/servlet-env)
(define-struct post (title body comments))
(define posts
(list
(make-post
"(Y Y) Works: The Why of Y"
"..."
(list
"First post! - A.T."
"Didn't I write this? - Matthias"))
(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"))))
(define (template section body)
`(html
(head (title "Alonzo's Church: " ,section)
(style ([type "text/css"])
,(make-cdata #f #f "
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 "Alonzo's Church: " ,section)
(div ([id "main"])
,@body))))
(define (blog-posted title body k-url)
`((h2 ,title)
(p ,body)
(h1 (a ([href ,k-url]) "Continue"))))
(define (extract-post req)
(define title (extract-binding/single 'title (request-bindings req)))
(define body (extract-binding/single 'body (request-bindings req)))
(set! posts
(list* (make-post title body empty)
posts))
(send/suspend
(lambda (k-url)
(template "Posted" (blog-posted title body k-url))))
(display-posts))
(define (blog-posts k-url)
(append
(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
(lambda (k-url)
(template "Posts" (blog-posts k-url))))))
(define (start req)
(display-posts))
(serve/servlet start)

View File

@ -0,0 +1,32 @@
<html>
<head>
<title>Alonzo's Church: @|section|</title>
<style type="text/css">
body {
margin: 0px;
padding: 10px;
}
#main {
background: #dddddd;
}
</style>
</head>
<body>
<script type="text/javascript">
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>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-YYYYYYY-Y");
pageTracker._trackPageview();
</script>
<h1>Alonzo's Church: @|section|</h1>
<div id="main">
@body
</div>
</body>
</html>

View File

@ -0,0 +1,46 @@
#lang scheme
(require web-server/templates
web-server/servlet
web-server/servlet-env)
(define-struct post (title body comments))
(define posts
(list
(make-post
"(Y Y) Works: The Why of Y"
"..."
(list
"First post! - A.T."
"Didn't I write this? - Matthias"))
(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"))))
(define (template section body)
(list TEXT/HTML-MIME-TYPE
(include-template "blog.html")))
(define (extract-post req)
(define title (extract-binding/single 'title (request-bindings req)))
(define body (extract-binding/single 'body (request-bindings req)))
(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)

View File

@ -2,11 +2,13 @@
@(require "web-server.ss")
@(require (for-label web-server/servlet
web-server/templates
scheme/promise
scheme/list
xml))
@(define xexpr @tech[#:doc '(lib "xml/xml.scrbl")]{X-expression})
@(define at-reader-ref @secref[#:doc '(lib "scribblings/scribble/scribble.scrbl")]{reader})
@(define text-ref @secref[#:doc '(lib "scribblings/scribble/scribble.scrbl")]{preprocessor})
@title[#:tag "templates"]{Templates}
@ -15,6 +17,9 @@
The @web-server provides a powerful Web template system for separating the presentation logic of a Web application
and enabling non-programmers to contribute to PLT-based Web applications.
@margin-note{Although all the examples here generate HTML, the template language and the @text-ref it is based on can
be used to generate any text-based format: C, SQL, form emails, reports, etc.}
@local-table-of-contents[]
@section{Static}
@ -62,8 +67,8 @@ Then
]
evaluates to the same content as the static example.
There is no constraints on the values, the way they are used, or the way they are defined, that are made accessible to the template.
For example,
There are no constraints on how the lexical context of the template is populated. For instance, you can built template abstractions
by wrapping the inclusion of a template in a function:
@schemeblock[
(define (fast-template thing)
(include-template "simple.html"))
@ -94,18 +99,71 @@ and
</html>
}|
Furthermore, there are no constraints on the Scheme used by templates: they can use macros, structs, continuation marks, threads, etc.
However, Scheme values that are ultimately returned must be printable by the @text-ref@"."
For example, consider the following outputs of the
title line of different calls to @scheme[fast-template]:
@itemize{
@item{
@schemeblock[
(fast-template 'Templates)
]
@verbatim[#:indent 2]|{
<head><title>Fastest Templates in the West!</title></head>
}|
}
@item{
@schemeblock[
(fast-template 42)
]
@verbatim[#:indent 2]|{
<head><title>Fastest 42 in the West!</title></head>
}|
}
@item{
@schemeblock[
(fast-template (list "Noo" "dles"))
]
@verbatim[#:indent 2]|{
<head><title>Fastest Noodles in the West!</title></head>
}|
}
@item{
@schemeblock[
(fast-template (lambda () "Thunks"))
]
@verbatim[#:indent 2]|{
<head><title>Fastest Thunks in the West!</title></head>
}|
}
@item{
@schemeblock[
(fast-template (delay "Laziness"))
]
@verbatim[#:indent 2]|{
<head><title>Fastest Laziness in the West!</title></head>
}|
}
}
@section{Gotchas}
One of the most important things to remember about the @at-reader-ref syntax is that the @"@" symbol must be escaped in content:
To obtain an @"@" symbol in template output, you must escape the @"@" symbol, because it is the escape character of the @at-reader-ref syntax.
For example, to obtain:
@verbatim[#:indent 2]|{
<html>
<head><title>Fastest @"@"s in the West!</title></head>
<body>
<h1>Bang!</h1>
<h2>Bang!</h2>
</body>
</html>
<head><title>Fastest @s in the West!</title></head>
}|
You must write:
@verbatim[#:indent 2]|{
<head><title>Fastest @"@"s in the West!</title></head>
}|
as your template: literal @"@"s must be replaced with @"@\"@\"".
The other gotcha is that since the template is compiled into a Scheme program, only its results will be printed. For example, suppose
we have the template:
@ -117,7 +175,7 @@ we have the template:
</table>
}|
If this is included in a lexical context with @scheme[clients] bound to @scheme[(list (cons "Young" "Brigham") (cons "Smith" "Joseph"))],
If this is included in a lexical context with @scheme[clients] bound to @schemeblock[(list (cons "Young" "Brigham") (cons "Smith" "Joseph"))]
then the template will be printed as:
@verbatim[#:indent 2]|{
<table>
@ -225,4 +283,218 @@ the template to be unescaped, then create a @scheme[cdata] structure:
(in c clients "<tr><td>" (car c) ", " (cdr c) "</td></tr>")
]
}
@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].
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))
(define posts
(list
(make-post
"(Y Y) Works: The Why of Y"
"..."
(list
"First post! - A.T."
"Didn't I write this? - Matthias"))
(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"))))
(code:comment "A function that is the generic template for the site")
(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 }")))
(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)
(div ([id "main"])
(code:comment "He had to be careful to use splicing here")
,@body))))
(define (blog-posted title body k-url)
`((h2 ,title)
(p ,body)
(h1 (a ([href ,k-url]) "Continue"))))
(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" (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
(lambda (k-url)
(template "Posts" (blog-posts k-url))))))
(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.
@filepath{blog.html}:
@verbatim[#:indent 2]|{
<html>
<head>
<title>Alonzo's Church: @|section|</title>
<style type="text/css">
body {
margin: 0px;
padding: 10px;
}
#main {
background: #dddddd;
}
</style>
</head>
<body>
<script type="text/javascript">
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>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-YYYYYYY-Y");
pageTracker._trackPageview();
</script>
<h1>Alonzo's Church: @|section|</h1>
<div id="main">
@body
</div>
</body>
</html>
}|
Notice that this part of the presentation is much simpler, because the CSS and JavaScript
can be included verbatim, without resorting to any special escape-escaping patterns.
Similarly, since the @scheme[body] is represented as a string, there is no need to
remember if splicing is necessary.
@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>
<form action="@|k-url|">
<input name="title" />
<input name="body" />
<input type="submit" />
</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)
]