diff --git a/collects/web-server/scribblings/templates.scrbl b/collects/web-server/scribblings/templates.scrbl index e04d3dbd06..aa688193b9 100644 --- a/collects/web-server/scribblings/templates.scrbl +++ b/collects/web-server/scribblings/templates.scrbl @@ -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]|{
-