On this page:
6.1 Basic Formlet Usage
6.2 Syntactic Shorthand
formlet
#%#
6.3 Functional Usage
xexpr-forest/ c
formlet/ c
formlet*/ c
pure
cross
cross*
xml-forest
xml
text
tag-xexpr
formlet-display
formlet-process
6.4 Predefined Formlets
make-input
make-input*
text-input
password-input
textarea-input
checkbox
multiselect-input
select-input
required
default
to-string
to-number
to-symbol
to-boolean
input-string
input-int
input-symbol
6.5 Utilities
send/ formlet
embed-formlet

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))

The rest of the manual gives the details of formlet usage and extension.

6.2 Syntactic Shorthand

Most users will want to use the syntactic shorthand for creating formlets.

(formlet rendering yields-expr)
Constructs a formlet with the specified rendering and the processing resulting in the yields-expr expression. The rendering form is a quasiquoted X-expression, with two special caveats:

,{=> formlet-expr name} embeds the formlet given by formlet-expr; the result of this processing this formlet is available in the yields-expr as name.
,{=> formlet-expr (values name ...)} embeds the formlet given by formlet-expr; the results of this processing this formlet is available in the yields-expr as name ....
(#%# xexpr ...) renders an X-expression forest.

Only allowed inside formlet.

6.3 Functional Usage

The syntactic shorthand abbreviates the construction of formlets with the following library. These combinators may be used directly to construct low-level formlets, such as those for new INPUT element types. Refer to Predefined Formlets for example low-level formlets using these combinators.

(formlet/c content ...)  contract?
  content : any/c
Equivalent to (-> integer? (values xexpr-forest/c (-> (listof binding?) (values (coerce-contract 'formlet/c  content) ...)) integer?)).

A formlet’s internal representation is a function from an initial input number to an X-expression forest rendering, a processing function, and the next allowable input number.

Equivalent to (formlet/c any/c ...).

(pure value)  (formlet/c any/c)
  value : any/c
Constructs a formlet that has no rendering and always returns value in the processing stage.

(cross f g)  (formlet/c any/c ...)
  f : (formlet/c procedure?)
  g : (formlet/c any/c ...)
Constructs a formlet with a rendering equal to the concatenation of the renderings of formlets f and g; a processing stage that applies g’s processing results to f’s processing result.

(cross* f g ...)  (formlet/c any/c)
  f : (formlet/c (() () #:rest (listof any/c) . ->* . any/c))
  g : (formlet/c any/c)
Equivalent to cross lifted to many arguments.

Constructs a formlet with the rendering r and the identity procedure as the processing step.

(xml r)  (formlet/c procedure?)
  r : xexpr/c
Equivalent to (xml-forest (list r)).

(text r)  (formlet/c procedure?)
  r : string?
Equivalent to (xml r).

(tag-xexpr tag attrs inner)  (formlet/c any/c)
  tag : symbol?
  attrs : (listof (list/c symbol? string?))
  inner : (formlet/c any/c)
Constructs a formlet with the rendering (list (list* tag attrs inner-rendering)) where inner-rendering is the rendering of inner and the processing stage identical to inner.

Renders f.

(formlet-process f r)  
any/c ...
  f : (formlet/c any/c ...)
  r : request?
Runs the processing stage of f on the bindings in r.

6.4 Predefined Formlets

These formlets are the main combinators for form input.

(make-input render)  (formlet/c (or/c false/c binding?))
  render : (string? . -> . xexpr/c)
This formlet is rendered with render, which is passed the input name, and results in the extracted binding.

(make-input* render)  (formlet/c (listof binding?))
  render : (string? . -> . xexpr/c)
This formlet is rendered with render, which is passed the input name, and results in all the bindings that use the name.

(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
This formlet renders using an INPUT element with the TEXT type and the attributes given in the arguments.

(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
This formlet renders using an INPUT element with the PASSWORD type and the attributes given in the arguments.

This formlet renders using an TEXTAREA element.

(checkbox value checked? [#:attributes attrs])
  (formlet/c (or/c false/c binding?))
  value : bytes?
  checked? : boolean?
  attrs : (listof (list/c symbol? string?)) = empty
This formlet renders using a INPUT elemen with the CHECKBOX type and the attributes given in the arguments.

(multiselect-input l    
  [#:multiple? multiple?    
  #:selected? selected?    
  #:display display])  (formlet/c list?)
  l : sequence?
  multiple? : boolean? = #t
  selected? : (any/c . -> . boolean?) = (λ (x) #f)
  display : (any/c . -> . xexpr/c) = (λ (x) x)
This formlet renders using an SELECT element with an OPTION for each element of the sequence. If multiple? is #t, then multiple options may be selected. An element is selected if selected? returns #t. Elements are displayed with display.

(select-input l    
  [#:selected? selected?    
  #:display display])  (formlet/c any/c)
  l : sequence?
  selected? : (any/c . -> . boolean?) = (λ (x) #f)
  display : (any/c . -> . xexpr/c) = (λ (x) x)
This formlet renders using an SELECT element with an OPTION for each element of the sequence. An element is selected if selected? returns #t. Elements are displayed with display.

(required f)  (formlet/c bytes?)
  f : (formlet/c (or/c false/c binding?))
Constructs a formlet that extracts the binding:form-value from the binding produced by f, or errors.

(default def f)  (formlet/c bytes?)
  def : bytes?
  f : (formlet/c (or/c false/c binding?))
Constructs a formlet that extracts the binding:form-value from the binding produced by f, or returns def.

(to-string f)  (formlet/c string?)
  f : (formlet/c bytes?)
Converts f’s output to a string. Equivalent to (cross (pure bytes->string/utf-8) f).

(to-number f)  (formlet/c number?)
  f : (formlet/c string?)
Converts f’s output to a number. Equivalent to (cross (pure string->number) f).

(to-symbol f)  (formlet/c symbol?)
  f : (formlet/c string?)
Converts f’s output to a symbol. Equivalent to (cross (pure string->symbol) f).

(to-boolean f)  (formlet/c boolean?)
  f : (formlet/c bytes?)
Converts f’s output to a boolean, if it is equal to #"on".

6.5 Utilities

A few utilities are provided for using formlets in Web applications.

(send/formlet f [#:wrap wrapper])  
any/c ...
  f : (formlet/c any/c ...)
  wrapper : (xexpr/c . -> . response/c)
   = 
(lambda (form-xexpr)
  `(html (head (title "Form Entry"))
         (body ,form-xexpr)))
Uses send/suspend to send f’s rendering (wrapped in a FORM tag whose action is the continuation URL (wrapped again by wrapper)) to the client. When the form is submitted, the request is passed to the processing stage of f.

(embed-formlet embed/url f)  xexpr/c
  embed/url : embed/url/c
  f : (formlet/c any/c ...)
Like send/formlet, but for use with send/suspend/dispatch.