7.7
Forms: Web Form Validation
Link to this document with
@other-doc['(lib "forms/forms.scrbl")]
Link to this document with
@other-doc['(lib "forms/forms.scrbl")]
1 Introduction
Link to this section with
@secref["intro" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["intro" #:doc '(lib "forms/forms.scrbl")]
This library lets you declaratively validate web form data (and
potentially json, xml and other structured data). It differs from the
formlets provided by Formlets: Functional Form Abstraction
in two important ways:
the validation model and the presentation are separate, and
you are given the ability to display and control validation errors.
1.1 Tutorial
Link to this section with
@secref["tutorial" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["tutorial" #:doc '(lib "forms/forms.scrbl")]
1.1.1 Validation
Link to this section with
@secref["validation" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["validation" #:doc '(lib "forms/forms.scrbl")]
forms are composed of other forms and formlets, functions
that can take an input, validate it and potentially transform it into
another value.
A basic form might look like this:
This form accepts an optional text value named "name" and
returns its upper-cased version. To validate some data against this
form we can call form-validate:
Formlets can be chained in order to generate more powerful
validations. If we wanted the above form to require the
"name" field, we’d combine binding/text with
required using ensure:
If we validate the same data against simple-form now, our
results differ slightly:
Notice how in the first example, an error was returned instead of
#f and we no longer need to guard against false values in our
lambda.
So far so good, but the syntax used to declare these forms can get
unwieldy as soon as your forms grow larger than a couple fields. The
library provides form*, which is a convenience macro to make
writing large forms more manageable. In day-to-day use, you’d declare
the above form like this:
If you’re thinking "Hey, that looks like a let"! You’re on
the right track.
1.1.2 Presentation
Link to this section with
@secref["Presentation" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["Presentation" #:doc '(lib "forms/forms.scrbl")]
Let’s take a slightly more complicated form:
This form expects a valid e-mail address and a password longer than 8
characters and it returns a list containing the two values on success.
To render this form to HTML, we can define a function that returns an
x-expression:
(define (render-login-form) | '(form | ((action "") | (method "POST")) | (label | "Username" | (input ((type "email") (name "username")))) | (label | (input ((type "password") (name "password")))) | (button ((type "submit")) "Login"))) |
|
This will do the trick, but it has two problems:
if there are any validation errors and we re-display the form to the user, the
previously-submitted values won’t show up,
nor will any validation errors.
We can use "widgets" to fix both problems. First, we have to update
render-login-form to take a widget rendering function as
input:
(define (render-login-form render-widget) | '(form | ((action "") | (method "POST")) | (label | "Username" | (input ((type "email") (name "username")))) | (label | "Password" | (input ((type "password") (name "password")))) | (button ((type "submit")) "Login"))) |
|
Second, instead of rendering the input fields ourselves, we can tell
render-widget to render the appropriate widgets for those fields:
(define (render-login-form render-widget) | `(form | ((action "") | (method "POST")) | (label | "Username" | ,(render-widget "username" (widget-email))) | (label | "Password" | ,(render-widget "password" (widget-password))) | (button ((type "submit")) "Login"))) |
|
Finally, we can also begin rendering errors:
(define (render-login-form render-widget) | `(form | ((action "") | (method "POST")) | (label | "Username" | ,(render-widget "username" (widget-email))) | ,@(render-widget "username" (widget-errors)) | (label | "Password" | ,(render-widget "password" (widget-password))) | ,@(render-widget "password" (widget-errors)) | (button ((type "submit")) "Login"))) |
|
To compose the validation and the presentation aspects, we can use
form-run:
(define (make-request #:method [method #"GET"] | #:url [url "http://example.com"] | #:headers [headers null] | #:bindings [bindings null]) | (request method (string->url url) headers (delay bindings) #f "127.0.0.1" 8000 "127.0.0.1")) |
|
form-run is smart enough to figure out whether or not the
request should be validated based on the request method. Because we
passed it a (fake) GET request above, it returned a 'pending
result and a widget renderer. That same renderer can be passed to our
render-login-form function:
|
> (pretty-print (render-login-form render-widget)) |
'(form | ((action "") (method "POST")) | (label "Username" (input ((type "email") (name "username")))) | (label "Password" (input ((type "password") (name "password")))) | (button ((type "submit")) "Login")) |
|
If we pass it an empty POST request instead, the data will be
validated and a 'failed result will be returned:
> (form-run login-form (make-request #:method #"POST")) |
'(failed ((username . "This field is required.") (password . "This field is required.")) #<procedure:...rivate/forms.rkt:101:2>) |
Finally, if we pass it a valid POST request, we’ll get a
'passed result:
|
'(passed ("bogdan@defn.io" "hunter1234") #<procedure:...rivate/forms.rkt:101:2>) |
Putting it all together, we might write a request handler that looks
like this:
1.1.3 Nested Validation
Link to this section with
@secref["nesting" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["nesting" #:doc '(lib "forms/forms.scrbl")]
I left one thing out of the tutorial that you might be wondering
about. Aside from plain values, forms can also return
ok? or err? values. This makes it possible to do
things like validate that two fields have the same value.
This form will validate that the two password fields contain the same
value and then return the first value.
1.1.4 Next Steps
Link to this section with
@secref["next-steps" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["next-steps" #:doc '(lib "forms/forms.scrbl")]
If the tutorial left you wanting for more, take a look at the
reference documentation below and also check out the
examples
folder in the source code repository.
1.2 Limitations
Link to this section with
@secref["limitations" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["limitations" #:doc '(lib "forms/forms.scrbl")]
The following features are not currently supported (and may never be):
multi-valued form bindings
dynamic lists of fields and forms – it is possible to
dynamically manipulate forms, but there is no nice syntax for it
default form values must be passed to form-run and cannot
be encoded into the forms themselves – this is inconvenient for
some use cases but it hugely simplifies the implementation and it
makes it so that the same form can be used, for example, to create
something and also to edit that thing
2 Reference
Link to this section with
@secref["reference" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["reference" #:doc '(lib "forms/forms.scrbl")]
2.1 Formlets
Link to this section with
@secref["formlets" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["formlets" #:doc '(lib "forms/forms.scrbl")]
Converts an optional
binding:form to a
string?,
ensuring that it contains something vaguely resembling an e-mail
address.
2.1.1 Primitives
Link to this section with
@secref["primitives" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["primitives" #:doc '(lib "forms/forms.scrbl")]
These functions produce formlets either by combining other formlets or
by "lifting" normal values into the formlet space.
Create a formlet that always returns x.
Create an errored formlet.
Sequence two or more formlets together, producing a formlet that
short-circuits on the first error.
2.1.2 Validators
Link to this section with
@secref["validators" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["validators" #:doc '(lib "forms/forms.scrbl")]
These functions produce basic validator formlets.
(required [#:message message])
|
|
message : string? = "This field is required." |
Ensures that a non-empty
string? value is present.
(matches pattern [#:message message])
|
|
pattern : regexp? |
| message | | : | | string? | | | | = | | (format "This field must match the regular expression ~v." p) |
|
Ensures that an optional
string? matches the given
pattern.
Ensures that an optional
string? is equal to one of the
cars of the provided list of pairs, producing the
cdr of the matched pair.
Ensures that an optional
string? is shorter than
n.
Ensures that an optional
string? is longer than
n.
2.2 Forms
Link to this section with
@secref["forms" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["forms" #:doc '(lib "forms/forms.scrbl")]
A form that can be used to validate children together and
produce a result value by passing the results of each field to
constructor.
(form* ([name formlet] ...+) | e ...+) |
|
Syntactic sugar for defining
forms.
Validate bindings against form.
Validate request against form.
2.3 Widgets
Link to this section with
@secref["widgets" #:doc '(lib "forms/forms.scrbl")]
Link to this section with
@secref["widgets" #:doc '(lib "forms/forms.scrbl")]
Various widget-related contracts.
Produce a widget renderer for a subform.
Produce a widget that can render errors.
Produce a widget that can render an INPUT element.
Produce a widget that can render a checkbox INPUT element.
Produce a widget that can render a email INPUT element.
Produce a widget that can render a file INPUT element.
Produce a widget that can render a hidden INPUT element.
Produce a widget that can render a number INPUT element.
Produce a widget that can render a password INPUT element.
Produce a widget that can render a SELECT element.
|
options : options/c |
attributes : attributes/c = null |
Produce a widget that can render a group of radio INPUTS.
Produce a widget that can render a text INPUT element.
Produce a widget that can render a textarea INPUT element.