Extended example
svn: r12550
This commit is contained in:
parent
bea2297421
commit
daff0abe15
|
@ -0,0 +1,4 @@
|
|||
<h2>@|title|</h2>
|
||||
<p>@|body|</p>
|
||||
|
||||
<h1><a href="@|k-url|">Continue</a></h1>
|
16
collects/tests/web-server/template/examples/blog-posts.html
Normal file
16
collects/tests/web-server/template/examples/blog-posts.html
Normal 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>
|
93
collects/tests/web-server/template/examples/blog-xexpr.ss
Normal file
93
collects/tests/web-server/template/examples/blog-xexpr.ss
Normal 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)
|
32
collects/tests/web-server/template/examples/blog.html
Normal file
32
collects/tests/web-server/template/examples/blog.html
Normal 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>
|
46
collects/tests/web-server/template/examples/blog.ss
Normal file
46
collects/tests/web-server/template/examples/blog.ss
Normal 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)
|
|
@ -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>
|
||||
|
@ -226,3 +284,217 @@ 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].
|
||||
|
||||
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)
|
||||
]
|
Loading…
Reference in New Issue
Block a user