6 Formlets: Functional Form Abstraction
(require web-server/formlets) |
The Web Server provides a kind of Web form abstraction called a formlet.
Formlets originate in the work of the Links research group in their paper The Essence of Form Abstraction.
6.1 Basic Formlet Usage
Suppose we want to create an abstraction of entering a date in an HTML form. The following formlet captures this idea:
(define date-formlet (formlet (div "Month:" ,{input-int . => . month} "Day:" ,{input-int . => . day}) (list month day)))
The first part of the formlet syntax is the template of an X-expression that is the rendering of the formlet. It can contain elements like ,(=> formlet name) where formlet is a formlet expression and name is an identifier bound in the second part of the formlet syntax.
This formlet is displayed (with formlet-display) as the following X-expression forest (list):
(list '(div "Month:" (input ([name "input_0"])) "Day:" (input ([name "input_1"]))))
date-formlet not only captures the rendering of the form, but also the request processing logic. If we send it an HTTP request with bindings for "input_0" to "10" and "input_1" to "3", with formlet-process, then it returns:
(list 10 3)
which is the second part of the formlet syntax, where month has been replaced with the integer represented by the "input_0" and day has been replaced with the integer represented by the "input_1".
The real power of formlet is that they can be embedded within one another. For instance, suppose we want to combine two date forms to capture a travel itinerary. The following formlet does the job:
(define travel-formlet (formlet (div "Name:" ,{input-string . => . name} (div "Arrive:" ,{date-formlet . => . arrive} "Depart:" ,{date-formlet . => . depart}) (list name arrive depart))))
(Notice that date-formlet is embedded twice.) This is rendered as:
(list '(div "Name:" (input ([name "input_0"])) (div "Arrive:" (div "Month:" (input ([name "input_1"])) "Day:" (input ([name "input_2"]))) "Depart:" (div "Month:" (input ([name "input_3"])) "Day:" (input ([name "input_4"]))))))
Observe that formlet-display has automatically generated unique names for each input element. When we pass bindings for these names to formlet-process, the following list is returned:
(list "Jay" (list 10 3) (list 10 6))
In all these examples, we used the input-int and input-string formlets. Any value with the formlet contract can be used in these positions. For example, (to-string (required (text-input))) could be used as well. The rest of the manual gives the details of formlet usage, extension, and existing formlet combinators.
6.2 Static Syntactic Shorthand
syntax
(formlet rendering-xexpr yields-expr)
(formlet (div ,@(for/list ([i (in-range 10)]) `(p ,((text-input) . => . name)))) name)
6.3 Dynamic Syntactic Shorthand
syntax
(formlet* rendering-expr yields-expr)
(formlet* `(div ,@(for/list ([i (in-range 1 10)]) `(p ,(number->string i) ,((text-input) . =>* . name)))) name)
(formlet* `(div (p ,((text-input) . =>* . name))) name)
6.4 Functional Usage
value
value
procedure
(xml-forest r) → (formlet/c procedure?)
r : xexpr-forest/c
procedure
(xml r) → (formlet/c procedure?)
r : xexpr/c
procedure
(text r) → (formlet/c procedure?)
r : string?
6.5 Predefined Formlets
procedure
(text-input [ #:value value #:size size #:max-length max-length #:read-only? read-only? #:attributes attrs]) → (formlet/c (or/c false/c binding?)) value : (or/c false/c bytes?) = #f size : (or/c false/c exact-nonnegative-integer?) = #f max-length : (or/c false/c exact-nonnegative-integer?) = #f read-only? : boolean? = #f attrs : (listof (list/c symbol? string?)) = empty
procedure
(password-input [ #:value value #:size size #:max-length max-length #:read-only? read-only? #:attributes attrs]) → (formlet/c (or/c false/c binding?)) value : (or/c false/c bytes?) = #f size : (or/c false/c exact-nonnegative-integer?) = #f max-length : (or/c false/c exact-nonnegative-integer?) = #f read-only? : boolean? = #f attrs : (listof (list/c symbol? string?)) = empty
procedure
(textarea-input [ #:value value #:rows rows #:cols cols #:attributes attrs]) → (formlet/c (or/c false/c binding?)) value : (or/c false/c bytes?) = #f rows : (or/c false/c number?) = #f cols : (or/c false/c number?) = #f attrs : (listof (list/c symbol? string?)) = empty
procedure
(checkbox value checked? [#:attributes attrs])
→ (formlet/c (or/c false/c binding?)) value : bytes? checked? : boolean? attrs : (listof (list/c symbol? string?)) = empty
procedure
(radio value checked? [#:attributes attrs])
→ (formlet/c (or/c false/c binding?)) value : bytes? checked? : boolean? attrs : (listof (list/c symbol? string?)) = empty
procedure
(radio-group l [ #:attributes attrs #:checked? checked? #:display display]) → (formlet/c any/c) l : sequence?
attrs : (any/c . -> . (listof (list/c symbol? string?))) = (λ (x) empty) checked? : (any/c . -> . boolean?) = (λ (x) #f) display : (any/c . -> . xexpr/c) = (λ (x) x)
procedure
(checkbox-group l [ #:attributes attrs #:checked? checked? #:display display]) → (formlet/c (listof any/c)) l : sequence?
attrs : (any/c . -> . (listof (list/c symbol? string?))) = (λ (x) empty) checked? : (any/c . -> . boolean?) = (λ (x) #f) display : (any/c . -> . xexpr/c) = (λ (x) x)
procedure
(submit value [#:attributes attrs])
→ (formlet/c (or/c false/c binding?)) value : bytes? attrs : (listof (list/c symbol? string?)) = empty
procedure
(reset value [#:attributes attrs])
→ (formlet/c (or/c false/c binding?)) value : bytes? attrs : (listof (list/c symbol? string?)) = empty
procedure
(file-upload [#:attributes attrs])
→ (formlet/c (or/c false/c binding?)) attrs : (listof (list/c symbol? string?)) = empty
procedure
(hidden value [#:attributes attrs])
→ (formlet/c (or/c false/c binding?)) value : bytes? attrs : (listof (list/c symbol? string?)) = empty
procedure
(img alt src [ #:height height #:longdesc ldesc #:usemap map #:width width #:attributes attrs]) → (formlet/c (or/c false/c binding?)) alt : bytes? src : bytes? height : (or/c false/c exact-nonnegative-integer?) = #f ldesc : (or/c false/c bytes?) = #f map : (or/c false/c bytes?) = #f width : (or/c false/c exact-nonnegative-integer?) = #f attrs : (listof (list/c symbol? string?)) = empty
procedure
(button type button-text [ #:disabled disabled #:value value #:attributes attrs]) → (formlet/c (or/c false/c binding?)) type : bytes? button-text : bytes? disabled : boolean? = #f value : (or/c false/c bytes?) = #f attrs : (listof (list/c symbol? string?)) = empty
procedure
(multiselect-input l [ #:attributes attrs #:multiple? multiple? #:selected? selected? #:display display]) → (formlet/c list?) l : sequence? attrs : (listof (list/c symbol? string?)) = empty multiple? : boolean? = #t selected? : (any/c . -> . boolean?) = (λ (x) #f) display : (any/c . -> . xexpr/c) = (λ (x) x)
procedure
(select-input l [ #:attributes attrs #:selected? selected? #:display display]) → (formlet/c any/c) l : sequence? attrs : (listof (list/c symbol? string?)) = empty selected? : (any/c . -> . boolean?) = (λ (x) #f) display : (any/c . -> . xexpr/c) = (λ (x) x)
procedure
(to-boolean f) → (formlet/c boolean?)
f : (formlet/c bytes?)
value
value
6.6 Utilities
procedure
(send/formlet f [ #:method method #:wrap wrapper]) →
any/c ... f : (formlet/c any/c ...) method : (or/c "GET" "POST" "get" "post") = "POST"
wrapper : (xexpr/c . -> . xexpr/c) =
(lambda (form-xexpr) `(html (head (title "Form Entry")) (body ,form-xexpr)))
procedure
(embed-formlet embed/url f) → xexpr/c
embed/url : ((request? . -> . any) . -> . string?) f : (formlet/c any/c ...)