6 Formlets: Functional Form Abstraction
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:
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:
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
Most users will want to use the syntactic shorthand for creating
formlets.
(formlet rendering-xexpr yields-expr)
|
Constructs a
formlet with the specified
rendering-xexpr and the processing
result is the evaluation of the
yields-expr expression. The
rendering-xexpr form is a quasiquoted
syntactic
X-expression, with three special caveats:
,{=> formlet-expr name} embeds the
formlet given by
formlet-expr; the result of 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 processing this formlet is
available in the
yields-expr as
name ....
6.3 Dynamic Syntactic Shorthand
The
formlet syntax is too restrictive for some applications because it forces the
rendering
to be
syntactically an
X-expression. You may discover you want to use a more "dynamic" shorthand.
Constructs a
formlet where
rendering-expr is evaluated (with caveats) to construct the rendering
and the processing result is the evaluation of the
yields-expr expression.
The
rendering-expr should evaluate to an "
X-expression" that may embed the results of the following forms
that only have meaning within
formlet*:
{=>* formlet-expr name} embeds the
formlet given by
formlet-expr; the result of 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 processing this formlet is
available in the
yields-expr as
name ....
Each of these forms evaluates to an opaque value that
rendering-expr may not manipulate in any way,
but if it is returned to
formlet* as part of an "
X-expression" it will be rendered and the formlets processing
stages will be executed, etc.
Because these forms
may appear anywhere in
rendering-expr, they may be duplicated. Therefore,
the formlet may render (and be processed) multiple times. Thus, in
yields-expr the formlet result names are
bound to lists of results rather than single results as in
formlet. The result list is ordered according
to the order of the formlets in the result of
rendering-expr. For example, in
name is bound to a list of strings, not a single string, where the first element is the string that
was inputted next to the string 1 on the Web page.
In this example, it is clear that this is the desired behavior. However, sometimes the value of a formlet’s
result may be surprising. For example, in
name is bound to a list of strings, because
formlet* cannot syntactically determine if
the formlet whose result is bound to
name is used many times.
6.4 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.
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.
(Actually,
formlet/c is a macro which avoids using
dynamic->*
when the number of range contracts for the processing function is known at compile time.)
Similar to the contracts created by
formlet/c, but uses
any
to avoid checking the results (or even specifying the number of results)
of the processing function.
Constructs a
formlet that has no rendering and always returns
value in
the processing stage.
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.
Equivalent to
cross lifted to many arguments.
Constructs a
formlet with the rendering
r and the
identity procedure as the processing step.
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.
Runs the processing stage of f on the bindings in r.
6.5 Predefined Formlets
These
formlets are the main combinators for form input.
This
formlet is rendered with
render, which is passed
the input name, and results in the extracted
binding.
This
formlet is rendered with
render, which is passed
the input name, and results in all the
bindings that use the
name.
This
formlet renders using an INPUT element with specified type
and arguments.
This
formlet renders using an INPUT element with the TEXT type
and the attributes given in the arguments.
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 with attributes
given in the arguments.
This
formlet renders using an INPUT element with the CHECKBOX
type and the attributes given in the arguments.
This
formlet renders using an INPUT element with the RADIO type and the attributes given in the arguments.
This
formlet renders using a sequence of INPUT elements of
RADIO type where each element gets its attributes from
attrs
that share a single NAME. An element is checked if
checked?
returns
#t. Elements are combined with the results of
display into an X-expression specified in
wrap.
The result of processing this formlet is a single
element of the sequence.
This
formlet renders using a sequence of INPUT elements of
CHECKBOX type where each element gets its attributes from
attrs that share a single NAME. An element is checked if
checked? returns
#t. Elements are followed by the
results of
display. The result of processing this formlet is
a list of elements of the sequence.
This
formlet renders using an INPUT element with the SUBMIT
type and the attributes given in the arguments.
This
formlet renders using an INPUT element with the RESET type
and the attributes given in the arguments.
This
formlet renders using an INPUT element with the FILE type
and the attributes given in the arguments.
This
formlet renders using an INPUT element with HIDDEN type
and the attributes given in the arguments.
This
formlet renders using an IMG element with the attributes
given in the arguments.
This
formlet renders using a BUTTON element with the attributes
given in the arguments.
button-text is the text that will
appear on the button when rendered.
This
formlet renders using an SELECT element with the
attributes given 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.
This
formlet renders using an SELECT element with the
attributes given with an OPTION for each element of the sequence. An
element is selected if
selected? returns
#t. Elements are displayed with
display.
Converts f’s output to a boolean, if it is equal to
#"on".
6.6 Utilities
A few utilities are provided for using
formlets in Web applications.
Uses
send/suspend and
response/xexpr to send
f’s rendering (wrapped in a FORM tag with method
method 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.
6.7 Formlets and Stateless Servlets
A few additional considerations apply when using formlets with
stateless #lang web-server servlets.
First of all, continuations captured in your servlet cannot
be serialized if they close over non-serializable data-structures.
There are some generally-applicable ways to avoid having a data structure
be part of the closure: for example, if you define all of your formlets
as module-level variables, they will never be part of closures and will not
need to be serialized.
However, sometimes it can be useful to create formlets dynamically.
To support this, all of the combinators documented above produce
formlets that are serializable (as long as they contain only serializable values).
This is not guaranteed to be true of third-party formlets.
One potential pitfall for formlets and serialization is
pure. Note that (serialize (pure +)) will fail,
because + is not serializable. To avoid this, you can
write (pure (λ args (apply + args))) (in #lang web-server,
where anonymous procedures are serializable, or using
web-server/lang/serial-lambda).
Secondly, stateless #lang web-server servlets are based on
different web interaction primitives than stateful servlets, so the version of
send/formlet from web-server/formlets will not work.
Instead, the library web-server/formlets/stateless provides
the same API as web-server/formlets, but with a version of
send/formlet for use in stateless servlets.
(Using web-server/formlets/stateless also provides all of the
bindings from web-server/formlets/lib, whereas
web-server/formlets provides only some of them.)
Alternatively, you can use the low-level formlet-process and
formlet-display procedures directly.
Another issue concerns capturing continuations within the processing
stage of a formlet. Recall that serializable continuations in
#lang web-server can only be captured from within
transformed contexts. The contract system is not transformed, so
the contracts on this library prevent capturing continuations
during the processing stage of formlets.
In most cases, the best solution is simply to avoid using
continuation-capturing operations during a formlet’s processing stage.
Instead, have the processing stage return a value, and interact with
the user based on that value in code outside of the formlet.
Alternatively, you can use generally-applicable approaches for capturing
continuations from untransformed contexts, such as
web-server/lang/native.
However, if neither of those approaches are satisfactory, the
library web-server/formlets/unsafe provides the same API as
web-server/formlets/stateless, but without enforcing
contracts. As the name implies, using web-server/formlets/unsafe
may produce inscrutable error messages and other unpleasant effects of programming
without contracts: you have been warned.