Introduction

Representing XHTML as X-expressions



"<html><head><title>A Title</title>

<body>A body</body></html>".



`(html (head (title "A title"))

(body "A body"))



`(body "The result of 1+2 is: " ,(number->string (+ 1 2)))



(body "The result of 1+2 is: " "3").



`(body ((color "white") (bgcolor "black"))

"Hello world")



The WEB library

> ( require ( planet "html.scm" ( "soegaard" "web.plt" 1 0 )))

> ( html-page #:body ` ( p "Hello world" ))

( html

( head ( title "A title" )

( link (( rel "stylesheet" ) ( type "text/css" ) ( href "" )))

( meta (( http-equiv "Content-Type" ) ( content "text/html;charset=UTF-8" ))))

( body ( h1 "A Header" )

( p "Hello world" )))

html-page

( override-default current-page-title "List it!" )

( override-default current-page-header ' ( h1 (( class "page_header" )) "List it!" ))

( override-default current-page-style-sheet "http://localhost/stylesheet.css" )

( html

( head ( title "List it!" )

( link (( rel "stylesheet" ) ( type "text/css" )

( href "http://localhost/stylesheet.css" )))

( meta (( http-equiv "Content-Type" )

( content "text/html;charset=UTF-8" ))))

( body ( h1 (( class "page_header" )) "List it!" )

( p "Hello world" )))

#:title, #:header, #:body, #:style-sheet, and #:style-sheet

html-pag

The Submit-new-entry page

html-submit-new-page

( define ( html-submit-new-page )

( html-page

#:title "List it! - submit"

#:header ' ( h1 "List it!" )

#:body

` ( div ( h2 "Submit a new entry" )

, ( html-form

"submitnewform" "control.scm"

( html-input "action" #:value "submit" #:type ' hidden )

` ( table ( tr ( td "url" ) ( td , ( html-input "url" #:type ' text #:value "http://" )))

( tr ( td "title" ) ( td , ( html-input "title" #:type ' text #:value "A title" ))))

( html-input "submit" #:value "submit" )))))

html-form

#:method

html-input

'image

'hidden

control.scm

The Frontpage

html-front-page

( define ( html-front-page page-number rank-of-first-entry entries )

( html-page

#:body ` ( div

, ( html-menu )

, ( html-list-of-entries page-number rank-of-first-entry entries ))))

html-menu

html-list-of-entries

The menu

( define ( html-menu )

` ( a (( href "control.scm?action=submitnew" )) "submit-new-link" ))

The list of entries

html-list-of-entries

( #4 ( "entry_id" "title" "url" "score" )

#4 ( "1" "Everything Scheme" "http://www.scheme.dk/blog/" "42" )

#4 ( "3" "PLT Scheme" "http://www.plt-scheme.org" "9" )

...)

( define ( html-list-of-entries page-number rank-of-first-entry entries )

` ( div (( class "entries" ))

,@ ( list-ec

( if ( not ( null? entries )))

( :list entry ( index i ) ( cdr entries ))

( :match # ( id header url score ) entry )

` ( table ...))))

(div (class "entries") (table ...) (table ...) ...)

(div (class "entries") ((table ...) (table ...) ...))

,@

(list entry (index i) (cdr entries))

i

id

header

url

score

`(table ...)

` ( table (( class "entry" ))

( tr ( td (( class "rank" )) , ( number->string ( + i rank-of-first-entry )))

( td , ( let (( form ( format "arrowform~a" id )))

( html-form form "control.scm"

#:atts ' (( class "arrows" ))

( html-input "arrowitem" #:type ' hidden )

( html-input "entry_id" #:type ' hidden #:value id )

( html-input "action" #:type ' hidden #:value "updown" )

` ( div , ( html-a-submit form "arrowitem" "up"

( html-icon ' go-up #:class "arrow" )))

, ( html-a-submit form "arrowitem" "down"

( html-icon ' go-down #:class "arrow" )))))

( td ( div ( a (( href , url )) , header ))

( span (( class "score" )) "score: " , score )))))))

control.scm?action=updown&arrowitem=down&entry_id=1 ?

control.scm

html-a-submit

( define ( html-a-submit formname formitem id text )

` ( a (( href , ( string-append

( format "javascript:document.~a.~a.value='~a';" formname formitem id )

( format "document.~a.submit();" formname ))))

, text ))

html-icon

`(img ...)

The Program



( module view mzscheme

( provide ( all-defined ))



( require ( lib "kw.ss" )

( planet "42.ss" ( "soegaard" "srfi.plt" ))

( planet "html.scm" ( "soegaard" "web.plt" 1 0 ))

( planet "web.scm" ( "soegaard" "web.plt" 1 0 )))





( override-default current-page-title "List it!" )

( override-default current-page-header ' ( h1 (( class "page_header" )) "List it!" ))

( override-default current-page-style-sheet "http://localhost/stylesheet.css" )





( define ( html-front-page page-number rank-of-first-entry entries )

( html-page

#:body ` ( div

, ( html-menu )

, ( html-list-of-entries page-number rank-of-first-entry entries ))))



( define ( html-menu )

` ( a (( href "control.scm?action=submitnew" )) "submit-new-link" ))



( define ( html-submit-new-page )

( html-page

#:title "List it! - submit"

#:header ' ( h1 "List it!" )

#:body

` ( div ( h2 "Submit a new entry" )

, ( html-form

"submitnewform" "control.scm"

( html-input "action" #:value "submit" #:type ' hidden )

` ( table ( tr ( td "url" ) ( td , ( html-input "url" #:type ' text #:value "http://" )))

( tr ( td "title" ) ( td , ( html-input "title" #:type ' text #:value "A title" ))))

( html-input "submit" #:value "submit" )))))



( define/kw ( html-icon name #:key ( class #f ))

( define ( icon-absolute-url name )

( format "/~a.png" name ))

( if class

` ( img (( class , class ) ( src , ( icon-absolute-url name ))))

` ( img ( ( src , ( icon-absolute-url name ))))))



( define ( html-list-of-entries page-number rank-of-first-entry entries )

` ( div (( class "entries" ))

,@ ( list-ec

( if ( not ( null? entries )))

( :list entry ( index i ) ( cdr entries ))

( :match # ( id header url score ) entry )

` ( table (( class "entry" ))

( tr ( td (( class "rank" )) , ( number->string ( + i rank-of-first-entry )))

( td , ( let (( form ( format "arrowform~a" id )))

( html-form form "control.scm"

#:atts ' (( class "arrows" ))

( html-input "arrowitem" #:type ' hidden )

( html-input "entry_id" #:type ' hidden #:value id )

( html-input "action" #:type ' hidden #:value "updown" )

` ( div , ( html-a-submit form "arrowitem" "up" ( html-icon ' go-up #:class "arrow" )))

( html-a-submit form "arrowitem" "down" ( html-icon ' go-down #:class "arrow" )))))

( td ( div ( a (( href , url )) , header ))

( span (( class "score" )) "score: " , score )))))))



( define ( html-redirect-page body )

( html-page #:title "Redirecting"

#:body body ))



)



First of all, thanks for the positive response to part 1 of this introduction . Comments are very welcome.The goal of this introduction is to write a "Mini Reddit" called "List It!". The first part focused on the model component i.e. the underlying data and how to implement it using an SQLite database. This second part will concentrate on the view component, which presents the data to the user. The third part will be on the controller.Our user interface consists of two pages: the frontpage shows the list of links with the highest scores and the submission page where a user can enter the name and url of a new entry. To give an impression of where we are headed, here is screenshots of the final result:In the Model-View-Controller organization, the controller receives a request from the client (user), retrieves data from the model, lets the view construct a web page and the sends a response back to the client. The view thus consists of a series of functions turning datainto web-pages.PLT Scheme supports multiple ways of representing web-pages. One option is to use strings:Since the structure of the document is lost with this choice, all but the most simple manipulations of the document, becomes harder to program than they should.Instead we will use X-expressions (S-expressions representing XML) to represent our web-pages. The above example becomes:Besides X-expressions another popular choice in the Scheme world is SXML (if you are into serious XML-manipulations look into SXML and SSAX ), which are similar to X-expressions in terms of use.The beauty of X-expressions is that, since they are normal lists, we can use the builtin functions to manipulate them. Another major convenience is we can use unquote (written as a comma) to insert the result of a Scheme expressions into the X-expression:which, when evaluated, results inAttributes are placed right after the tag:To avoid writting boilerplate stuff (charset, stylesheet, content type etc.) each and every time a web page is constructed, we will use the general web framework from PLaneT (the PLT Scheme "CPAN" - note that packages are downloaded automatically if needed).In the example above, theonly received the body, so it used the site wide default arguments for the title, the stylesheet and the "header". Obviously we need to override the defaults for our List It! site:After overriding the defaults we get:Use the keywordswithe, in order not to use the site wide defaults.The funtionbuilds an X-expression representing the submission page. It takes no arguments, since it doesn't rely on data from the model:The submission page consists of a form with two input fields. One for the url and one for the title. The functionrececives a name of the form, the action. The default method for submitting is POST, but one can use the keyword argumentto choose something else. The functionbuilds an input field. Other types of input fields are:andClicking the submit button will post the parameters action, url and title toThe submitted entries are ranked according to their score. The entry with the highest score has rank 1, the next rank 2 and so on. Only a limited number of entries can be showed at a time, so we imagine the entries divided into pages. The functionbelow receives three arguments: a page number, the rank of the first entry of the page in question and the list of entriesThe functiongenerates an X-expression for the "menu" on top of the screen The functiongenerates an X-expression representing the list of entries.The "menu" consists so far of a single menu item "submit-new-link".The list of entries is received byas a list of vectors.The first vector holds the column names in the database, and can thus safely be ignored.A DIV of class "entries" is used around the list of entries, so we single them out in the stylesheet. Next we use an eager comprehension list-ec to generate a list of X-expressions, one for each entry. Since we want the result to be of the formand notwe use unquote-splicinginstead of just unquote.The eager comprehension first checks to see, whether entries is the empty list - and if so, an empty list of X-expressions is generated.The clauseiterates over the elements of the list entries (minus the first element), binding entry to each element in turn, also the index variablecounts from 0 and up.The clause (:match #(id header url score) entry) uses pattern matching to bind the variables, andto the elements of the entry vector.The final expressioncan now use the variables to generate the X-expression for the entry in question.The table is used to position the rank, the up- and down-arrow and the link. I am able to do the same in CSS - but not in all browsers at the same time.The alert reader will probably wonder, why the form is introduced. Why aren't the arrows two simple links to, say,Well, a click at the above url would result in the entry getting a vote. The problem lies in what happens next. If the user now reloads the page, the entry will get an extra vote. Bookingmarking the page will also result in extra votes, when the user returns.The form-solution sends the parameters "behind the scenes", so that the url becomes. This obviously solves the bookmarking problem. The reload problem is solved via the Post/Redirect/Get pattern - which I'll discuss in detail in the post on the controller.The functionbuilds a link that submits the form (it's in the web-framework), it involves a little JavaScript.The functionreturns an X-expressionrepresenting various images. In this tutorial only the up- and down-arrow is supported, but if anyone is interested, I have a version supporting all of Tango (an excellent source of icons).