I’ve mentioned the boundary issue already: whenever data enters or leaves an application, we need to validate it. Probably the most difficult place this occurs is in forms. Coding forms is complex; in an ideal world, we’d like a solution that can do all of the following:
Ensure data is valid.
Marshal string data in the form submission to Haskell data types.
Generate HTML code for displaying the form.
Generate JavaScript to do client-side validation and provide more user-friendly widgets, such as date pickers.
Build up more complex forms by combining together simpler forms.
Automatically assign names to our fields that are guaranteed to be unique.
The yesod-form
package provides all these features in a simple, declarative
API. It builds on top of Yesod’s widgets to simplify styling of forms and
applying JavaScript appropriately. And like the rest of Yesod, it uses
Haskell’s type system to make sure everything is working correctly.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import
Control.Applicative
((
<$>
),
(
<*>
))
import
Data.Text
(
Text
)
import
Data.Time
(
Day
)
import
Yesod
import
Yesod.Form.Jquery
data
App
=
App
mkYesod
"App"
[
parseRoutes
|
/
HomeR
GET
/
person
PersonR
POST
|
]
instance
Yesod
App
-- Tells our application to use the standard English messages.
-- If you want i18n, then you can supply a translating function instead.
instance
RenderMessage
App
FormMessage
where
renderMessage
_
_
=
defaultFormMessage
-- And tell us where to find the jQuery libraries. We'll just use the defaults,
-- which point to the Google CDN.
instance
YesodJquery
App
-- The data type we wish to receive from the form
data
Person
=
Person
{
personName
::
Text
,
personBirthday
::
Day
,
personFavoriteColor
::
Maybe
Text
,
personEmail
::
Text
,
personWebsite
::
Maybe
Text
}
deriving
Show
-- Declare the form. The type signature is a bit intimidating, but here's the
-- overview:
--
-- * The Html parameter is used for encoding some extra information. See the
-- discussion regarding runFormGet and runFormPost below for further
-- explanation.
--
-- * We have our Handler as the inner monad, which indicates which site this is
-- running in.
--
-- * FormResult can be in three states: FormMissing (no data available),
-- FormFailure (invalid data), and FormSuccess.
--
-- * The Widget is the viewable form to place into the web page.
--
-- Note that the scaffolded site provides a convenient Form type synonym,
-- so that our signature could be written as:
--
-- > personForm :: Form Person
--
-- For our purposes, it's good to see the long version.
personForm
::
Html
->
MForm
Handler
(
FormResult
Person
,
Widget
)
personForm
=
renderDivs
$
Person
<$>
areq
textField
"Name"
Nothing
<*>
areq
(
jqueryDayField
def
{
jdsChangeYear
=
True
-- give a year drop-down
,
jdsYearRange
=
"1900:-5"
-- 1900 to five years ago
})
"Birthday"
Nothing
<*>
aopt
textField
"Favorite color"
Nothing
<*>
areq
emailField
"Email address"
Nothing
<*>
aopt
urlField
"Website"
Nothing
-- The GET handler displays the form
getHomeR
::
Handler
Html
getHomeR
=
do
-- Generate the form to be displayed
(
widget
,
enctype
)
<-
generateFormPost
personForm
defaultLayout
[
whamlet
|
<
p
>
The
widget
generated
contains
only
the
contents
of
the
form
,
not
the
form
tag
itself
.
So
...
<
form
method
=
post
action
=@
{
PersonR
}
enctype
=#
{
enctype
}
>
^
{
widget
}
<
p
>
It
also
doesn't
include
the
submit
button
.
<
button
>
Submit
|
]
-- The POST handler processes the form. If it is successful, it displays the
-- parsed person. Otherwise, it displays the form again with error messages.
postPersonR
::
Handler
Html
postPersonR
=
do
((
result
,
widget
),
enctype
)
<-
runFormPost
personForm
case
result
of
FormSuccess
person
->
defaultLayout
[
whamlet
|<
p
>#
{
show
person
}
|
]
_
->
defaultLayout
[
whamlet
|
<
p
>
Invalid
input
,
let's
try
again
.
<
form
method
=
post
action
=@
{
PersonR
}
enctype
=#
{
enctype
}
>
^
{
widget
}
<
button
>
Submit
|
]
main
::
IO
()
main
=
warp
3000
App
Before jumping into the types themselves, we should begin with an overview of the different kinds of forms. There are three categories:
These are the most commonly used (it’s what appeared in the preceding code). This approach has some nice properties: it lets error messages coalesce and remains a very high-level, declarative approach. (For more information on applicative code, see the Haskell wiki.)
A more powerful alternative to the applicative style. Although this approach allows for more flexibility, it does so at the cost of being more verbose. However, it’s useful if you want to create forms that don’t fit into the standard two-column look.
Used only for receiving input. No HTML is generated for receiving the user input. Useful for interacting with existing forms.
In addition, there are a number of different variables that come into play for each form and field you will want to set up:
Is the field required or optional?
Should it be submitted with GET
or POST
?
Does it have a default value, or not?
An overriding goal is to minimize the number of field definitions and let them
work in as many contexts as possible. One result of this is that we end up with
a few extra words for each field. In the synopsis, you may have noticed things
like areq
and that extra Nothing
parameter. We’ll cover why all of those
exist in the course of this chapter, but for now realize that by making these
parameters explicit, we are able to reuse the individual fields (like
intField
) in many different ways.
A quick note on naming conventions: each form type has a one-letter prefix (A
, M
, or I
) that is used in a few places, such as MForm
. We also use req
and opt
to mean required and optional. Combining these, we create a required
applicative field with areq
, or an optional input field with iopt
.
The Yesod.Form.Types
module declares a few types. We won’t cover all the types
available, but will instead focus on the most crucial. Let’s start with some of
the simple ones:
enctype
The encoding type, either UrlEncoded
or Multipart
. This data type
declares an instance of ToHtml
, so you can use the enctype
directly in
Hamlet.
FormResult
This data type has one of three possible states: FormMissing
if no data was
submitted, FormFailure
if there was an error parsing the form (e.g., missing
a required field or including invalid content), or FormSuccess
if everything went
smoothly.
FormMessage
Represents all of the different messages that can be generated as
a data type. For example, MsgInvalidInteger
is used by the library to
indicate that the textual value provided is not an integer. By keeping this
data highly structured, you are able to provide any kind of rendering function
you want, which allows for internationalization (i18n) of your application.
Next, we have some data types used for defining individual fields. We define a field as a single piece of information, such as a number, a string, or an email address. Fields are combined to build forms. The two key data types here are:
Field
Defines two pieces of functionality: how to parse the text input from a
user into a Haskell value, and how to create the widget to be displayed to the
user. yesod-form
defines a number of individual Field+s in +Yesod.Form.Fields
.
FieldSettings
Contains basic information on how a field should be displayed, such as
the display name, an optional tooltip, and possibly hardcoded id
and name
attributes. (If none are provided, they are automatically generated.) Note that
FieldSettings
provides an IsString
instance, so when you need to provide a
FieldSettings
value, you can actually type in a literal string. That’s how we
interacted with it in the synopsis.
And finally, we get to the important stuff: the forms themselves. There are
three types: MForm
is for monadic forms, AForm
for applicative, and
FormInput
for input. MForm
is actually a type synonym for a
monad stack that provides the following features:
A Reader
monad giving us the parameters submitted by the user, the
foundation data type, and the list of languages the user supports. The last two
are used for rendering of the FormMessage
s to support i18n (more on this
later).
A Writer
monad keeping track of the Enctype
. A form will be
UrlEncoded
by default unless there is a file input field, which will force us to use
Multipart
instead.
A State
monad keeping track of generated names and identifiers for fields.
An AForm
is pretty similar. However, there are a few major differences:
It produces a list of FieldView
s, which are used for tracking what we
will display to the user. This allows us to keep an abstract idea of the form
display, and then at the end of the day choose an appropriate function for
laying it out on the page. In the synopsis, we used renderDivs
, which
creates a bunch of <div>
tags. Two other options are renderBootstrap
and
renderTable
.
It does not provide a Monad
instance. The goal of Applicative
is to allow
the entire form to run, grab as much information on each field as possible,
and then create the final result. This cannot work in the context of Monad
.
A FormInput
is even simpler: it returns either a list of error messages or a
result.
“But wait a minute,” you say. “You said the synopsis code uses an applicative form,
but I’m sure the type signature said MForm
. Shouldn’t it be monadic?” That’s
true; the final form we produced was monadic. But what really happened is that
we converted an applicative form to a monadic one.
Again, our goal is to reuse code as much as possible, and minimize the number
of functions in the API. And monadic forms are more powerful than applicative forms,
if a bit clumsy, so anything that can be expressed in an applicative form could
also be expressed in a monadic form. There are two core functions that help out
with this: aformToForm
converts any applicative form to a monadic one, and
formToAForm
converts certain kinds of monadic forms to applicative forms.
“But wait another minute,” you insist. “I didn’t see any aformToForm
!”
Also true. The renderDivs
function takes care of that for us.
Now that I’ve (hopefully) convinced you that we were really dealing with applicative forms, let’s have a look and try to understand how these things get created. Let’s take a simple example:
data
Car
=
Car
{
carModel
::
Text
,
carYear
::
Int
}
deriving
Show
carAForm
::
AForm
Handler
Car
carAForm
=
Car
<$>
areq
textField
"Model"
Nothing
<*>
areq
intField
"Year"
Nothing
carForm
::
Html
->
MForm
Handler
(
FormResult
Car
,
Widget
)
carForm
=
renderTable
carAForm
Here, we’ve explicitly split up applicative and monadic forms. In carAForm
,
we use the <$>
and <*>
operators. This should not be surprising; these are
almost always used in applicative-style code. And we have one line for each
record in our Car
data type. Perhaps also unsurprisingly, we have a
textField
for the Text
record, and an intField
for the Int
record.
Let’s look a bit more closely at the areq
function. Its (simplified) type
signature is Field a -> FieldSettings -> Maybe a -> AForm a
. That
first argument specifies the data type of this field, how to parse
it, and how to render it. The next argument, FieldSettings
, tells us the
label, tooltip, name, and ID of the field. In this case, we’re using the
previously mentioned IsString
instance of FieldSettings
.
And what’s up with that Maybe a
? It provides the optional default value. For
example, if we wanted our form to fill in “2007” as the default car year, we
would use areq intField "Year" (Just 2007)
. We can even take this to the next
level, and have a form that takes an optional parameter giving the default
values:
carAForm
::
Maybe
Car
->
AForm
Handler
Car
carAForm
mcar
=
Car
<$>
areq
textField
"Model"
(
carModel
<$>
mcar
)
<*>
areq
intField
"Year"
(
carYear
<$>
mcar
)
Suppose we wanted to have an optional field (like the car color). All we do for this is use the aopt
function:
carAForm
::
AForm
Handler
Car
carAForm
=
Car
<$>
areq
textField
"Model"
Nothing
<*>
areq
intField
"Year"
Nothing
<*>
aopt
textField
"Color"
Nothing
Like with required fields, the last argument is the optional default value.
However, this has two layers of Maybe
wrapping. This is actually a bit
redundant, but it makes it much easier to write code that takes an optional default form parameter, such as in the next example:
carAForm
::
Maybe
Car
->
AForm
Handler
Car
carAForm
mcar
=
Car
<$>
areq
textField
"Model"
(
carModel
<$>
mcar
)
<*>
areq
intField
"Year"
(
carYear
<$>
mcar
)
<*>
aopt
textField
"Color"
(
carColor
<$>
mcar
)
carForm
::
Html
->
MForm
Handler
(
FormResult
Car
,
Widget
)
carForm
=
renderTable
$
carAForm
$
Just
$
Car
"Forte"
2010
$
Just
"gray"
How would we make our form only accept cars created after 1990? If you
remember, the Field
itself contained the information on
what is a valid entry. So all we need to do is write a new Field
, right?
Well, that would be a bit tedious. Instead, let’s just modify an existing one:
carAForm
::
Maybe
Car
->
AForm
Handler
Car
carAForm
mcar
=
Car
<$>
areq
textField
"Model"
(
carModel
<$>
mcar
)
<*>
areq
carYearField
"Year"
(
carYear
<$>
mcar
)
<*>
aopt
textField
"Color"
(
carColor
<$>
mcar
)
where
errorMessage
::
Text
errorMessage
=
"Your car is too old, get a new one!"
carYearField
=
check
validateYear
intField
validateYear
y
|
y
<
1990
=
Left
errorMessage
|
otherwise
=
Right
y
The trick here is the check
function. It takes a function (validateYear
)
that returns either an error message or a modified field value. In this
example, we haven’t modified the value at all. That is usually going to be the
case. This kind of checking is very common, so we have a shortcut:
carYearField
=
checkBool
(
>=
1990
)
errorMessage
intField
checkBool
takes two parameters: a condition that must be fulfilled, and an
error message to be displayed if it was not.
You may have noticed the explicit Text
type signature on
errorMessage
. In the presence of OverloadedStrings
, this is necessary. In
order to support i18n, messages can have many different data types, and GHC has
no way of determining which instance of IsString
you intended to use.
It’s great to make sure the car isn’t too old. But what if we want to make sure
that the year specified is not in the future? In order to look up the current
year, we’ll need to run some IO
. For such circumstances, we’ll need checkM
,
which allows our validation code to perform arbitrary actions:
carYearField
=
checkM
inPast
$
checkBool
(
>=
1990
)
errorMessage
intField
inPast
y
=
do
thisYear
<-
liftIO
getCurrentYear
return
$
if
y
<=
thisYear
then
Right
y
else
Left
(
"You have a time machine!"
::
Text
)
getCurrentYear
::
IO
Int
getCurrentYear
=
do
now
<-
getCurrentTime
let
today
=
utctDay
now
let
(
year
,
_
,
_
)
=
toGregorian
today
return
$
fromInteger
year
inPast
is a function that will return an Either
result in the Handler
monad. We use liftIO getCurrentYear
to get the current year and then compare
it against the user-supplied year. Also, notice how we can chain together
multiple validators.
Because the checkM
validator runs in the Handler
monad, it has access
to a lot of the stuff you can normally do in Yesod. This is especially useful
for running database actions, which we’ll cover in Chapter 10.
Our color entry field is nice, but it’s not exactly user-friendly. What we really want is a drop-down list:
data
Car
=
Car
{
carModel
::
Text
,
carYear
::
Int
,
carColor
::
Maybe
Color
}
deriving
Show
data
Color
=
Red
|
Blue
|
Gray
|
Black
deriving
(
Show
,
Eq
,
Enum
,
Bounded
)
carAForm
::
Maybe
Car
->
AForm
Handler
Car
carAForm
mcar
=
Car
<$>
areq
textField
"Model"
(
carModel
<$>
mcar
)
<*>
areq
carYearField
"Year"
(
carYear
<$>
mcar
)
<*>
aopt
(
selectFieldList
colors
)
"Color"
(
carColor
<$>
mcar
)
where
colors
::
[(
Text
,
Color
)]
colors
=
[(
"Red"
,
Red
),
(
"Blue"
,
Blue
),
(
"Gray"
,
Gray
),
(
"Black"
,
Black
)]
selectFieldList
takes a list of pairs. The first item in the pair is the text displayed to the user in the drop-down list, and the second item is the actual Haskell value. Of course, this code looks really repetitive; we can get the same result using the Enum
and Bounded
instance GHC automatically derives for us:
colors
=
map
(
pack
.
show
&&&
id
)
[
minBound
..
maxBound
]
[minBound..maxBound]
gives us a list of all the different Color
values. We
then apply a map
and &&&
(a.k.a., the to turn that into a
list of pairs. And even this can be simplified by using the optionsEnum
function provided by yesod-form
, which would turn our original code into:
carAForm
::
Maybe
Car
->
AForm
Handler
Car
carAForm
mcar
=
Car
<$>
areq
textField
"Model"
(
carModel
<$>
mcar
)
<*>
areq
carYearField
"Year"
(
carYear
<$>
mcar
)
<*>
aopt
(
selectFieldList
optionsEnum
)
"Color"
(
carColor
<$>
mcar
)
Some people prefer radio buttons to drop-down lists. Fortunately, this is just a one-word change:
carAForm
=
Car
<$>
areq
textField
"Model"
Nothing
<*>
areq
intField
"Year"
Nothing
<*>
aopt
(
radioFieldList
optionsEnum
)
"Color"
Nothing
At some point, we’re going to need to take our beautiful forms and produce some results. There are a number of different functions available for this, each with its own purpose. I’ll go through them, starting with the most common:
runFormPost
This will run your form against any submitted POST
parameters.
If this is not a POST
submission, it will return FormMissing
. This
automatically inserts a security token as a hidden form field to avoid
cross-site request forgery (CSRF) attacks.
runFormGet
The equivalent of runFormPost
for GET
parameters. In order to
distinguish a normal GET
page load from a GET
submission, it includes an
extra _hasdata
hidden field in the form. Unlike runFormPost
, it does
not include CSRF protection.
runFormPostNoToken
Same as runFormPost
, but does not include (or require)
the CSRF security token.
generateFormPost
Instead of binding to existing POST
parameters, acts as if
there are none. This can be useful when you want to generate a new form after a
previous form was submitted, such as in a wizard.
generateFormGet
The return type from the first three is ((FormResult a, Widget), Enctype)
.
The Widget
will already have any validation errors and previously submitted
values.
There have been a few references to i18n in this chapter. The topic will get
more thorough coverage in Chapter 22, but because it has such a profound
effect on yesod-form
, I wanted to give a brief overview here. The idea behind i18n
in Yesod is to have data types represent messages. Each site can have an
instance of RenderMessage
for a given data type, which will translate that
message based on a list of languages the user accepts. As a result of all this,
there are a few things you should be aware of:
There is an automatic instance of RenderMessage
for Text
in every site,
so you can just use plain strings if you don’t care about i18n support.
However, you may need to use explicit type signatures occasionally.
yesod-form
expresses all of its messages in terms of the FormMessage
data type. Therefore, to use yesod-form
, you’ll need to have an appropriate RenderMessage
instance. A simple one that uses the default English translations would be:
instance RenderMessage App FormMessage where renderMessage _ _ = defaultFormMessage
This is provided automatically by the scaffolded site.
Oftentimes, a simple form layout is adequate, and applicative forms excel at this approach. Sometimes, however, you’ll want your form to have a more customized look, such as that shown in Figure 8-1.
For these use cases, monadic forms fit the bill. They are a bit more verbose than their applicative cousins, but this verbosity allows you to have complete control over what the form will look like. In order to generate the form in Figure 8-1, we could use code like the following:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import
Control.Applicative
import
Data.Text
(
Text
)
import
Yesod
data
App
=
App
mkYesod
"App"
[
parseRoutes
|
/
HomeR
GET
|
]
instance
Yesod
App
instance
RenderMessage
App
FormMessage
where
renderMessage
_
_
=
defaultFormMessage
data
Person
=
Person
{
personName
::
Text
,
personAge
::
Int
}
deriving
Show
personForm
::
Html
->
MForm
Handler
(
FormResult
Person
,
Widget
)
personForm
extra
=
do
(
nameRes
,
nameView
)
<-
mreq
textField
"this is not used"
Nothing
(
ageRes
,
ageView
)
<-
mreq
intField
"neither is this"
Nothing
let
personRes
=
Person
<$>
nameRes
<*>
ageRes
let
widget
=
do
toWidget
[
lucius
|
##
{
fvId
ageView
}
{
width
:
3
em
;
}
|
]
[
whamlet
|
#
{
extra
}
<
p
>
Hello
,
my
name
is
#
^
{
fvInput
nameView
}
and
I
am
#
^
{
fvInput
ageView
}
years
old
.
#
<
input
type
=
submit
value
=
"Introduce myself"
>
|
]
return
(
personRes
,
widget
)
getHomeR
::
Handler
Html
getHomeR
=
do
((
res
,
widget
),
enctype
)
<-
runFormGet
personForm
defaultLayout
[
whamlet
|
<
p
>
Result:
#
{
show
res
}
<
form
enctype
=#
{
enctype
}
>
^
{
widget
}
|
]
main
::
IO
()
main
=
warp
3000
App
Similar to the applicative areq
, we use mreq
for monadic forms. (And yes,
there’s also mopt
for optional fields.) But there’s a big difference: mreq
gives us back a pair of values. Instead of hiding away the FieldView
value and
automatically inserting it into a widget, we have the ability to insert it as
we see fit.
FieldView
has a number of pieces of information. The most important is
fvInput
, which is the actual form field. In this example, we also use fvId
,
which gives us back the HTML id
attribute of the <input>
tag. In our example,
we use that to specify the width of the field.
You might be wondering what the story is with the “this is not used” and
“neither is this” values. mreq
takes FieldSettings
as its second
argument. FieldSettings
provides an IsString
instance, so the strings
are essentially expanded by the compiler as follows:
fromString
"this is not used"
==
FieldSettings
{
fsLabel
=
"this is not used"
,
fsTooltip
=
Nothing
,
fsId
=
Nothing
,
fsName
=
Nothing
,
fsAttrs
=
[]
}
In the case of applicative forms, the fsLabel
and fsTooltip
values are used
when constructing your HTML. In the case of monadic forms, Yesod does not
generate any of the “wrapper” HTML for you, and therefore these values are
ignored. However, we still keep the FieldSettings
parameter to allow you to
override the id
and name
attributes of your fields if desired.
The other interesting bit is the extra
value. GET
forms include an extra
field to indicate that they have been submitted, and POST
forms include a
security token to prevent CSRF attacks. If you don’t include this extra hidden
field in your form, the form submission will fail.
Other than that, things are pretty straightforward. We create our personRes
value by combining the nameRes
and ageRes
values, and then return
a tuple of the person and the widget. And in the getHomeR
function,
everything looks just like an applicative form. In fact, you could swap our monadic form with an applicative one and the code would still work.
Applicative and monadic forms handle both the generation of your HTML code and the parsing of user input. Sometimes you only want to do the latter, such as when there’s an already existing form in HTML somewhere, or if you want to generate a form dynamically using JavaScript. In such a case, you’ll want input forms.
These work mostly the same as applicative and monadic forms, with some differences:
You use runInputPost
and runInputGet
.
You use ireq
and iopt
. These functions now only take two arguments: the
field type and the name (i.e., HTML name
attribute) of the field in
question.
After running a form, it returns the value. It doesn’t return a widget or an encoding type.
If there are any validation errors, the page returns an “invalid arguments” error page.
You can use input forms to re-create the previous example. Note, however, that the input version is less user-friendly. If you make a mistake in an applicative or monadic form, you will be brought back to the same page, with your previously entered values in the form, and an error message explaining what you need to correct. With input forms, the user simply gets an error message:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import
Control.Applicative
import
Data.Text
(
Text
)
import
Yesod
data
App
=
App
mkYesod
"App"
[
parseRoutes
|
/
HomeR
GET
/
input
InputR
GET
|
]
instance
Yesod
App
instance
RenderMessage
App
FormMessage
where
renderMessage
_
_
=
defaultFormMessage
data
Person
=
Person
{
personName
::
Text
,
personAge
::
Int
}
deriving
Show
getHomeR
::
Handler
Html
getHomeR
=
defaultLayout
[
whamlet
|
<
form
action
=@
{
InputR
}
>
<
p
>
My
name
is
<
input
type
=
text
name
=
name
>
and
I
am
<
input
type
=
text
name
=
age
>
years
old
.
<
input
type
=
submit
value
=
"Introduce myself"
>
|
]
getInputR
::
Handler
Html
getInputR
=
do
person
<-
runInputGet
$
Person
<$>
ireq
textField
"name"
<*>
ireq
intField
"age"
defaultLayout
[
whamlet
|<
p
>#
{
show
person
}
|
]
main
::
IO
()
main
=
warp
3000
App
The fields that come built in with Yesod will likely cover the vast majority of
your form needs. But occasionally, you’ll need something more specialized.
Fortunately, you can create new fields in Yesod yourself. The Field
constructor
has three values. The first, fieldParse
, takes a list of values submitted by the user and
returns one of three results:
An error message saying validation failed
The parsed value
Nothing, indicating that no data was supplied
That last case might sound surprising. It would seem that Yesod can automatically know that no information is supplied when the input list is empty. But in reality, for some field types, the lack of any input is actually valid input. Checkboxes, for instance, indicate an unchecked state by sending in an empty list.
Also, what’s up with the list? Shouldn’t it be a Maybe
? That’s also not the
case. With grouped checkboxes and multiselect lists, you’ll have multiple
widgets with the same name. We also use this trick in our example.
The second value in the constructor is fieldView
, and it renders a widget to display to the
user. This function has the following arguments:
The id
attribute.
The name
attribute.
Any other arbitrary attributes.
The result, given as an Either
value. This will provide either the unparsed input (when parsing failed) or the successfully parsed value. intField
is a great example of how this works. If you type in 42
, the value of the result will be Right 42
. But if you type in turtle
, the result will be Left "turtle"
. This lets you put in a value
attribute on your <input>
tag that will give the user a consistent experience.
A Bool
indicating if the field is required.
The final value in the constructor is fieldEnctype
. If you’re dealing with
file uploads, this should be Multipart
; otherwise, it should be UrlEncoded
.
As a small example, let’s create a new field type that is a password confirm field. This field has two text inputs—both with the same name
attribute—and returns an error message if the values don’t match. Note that, unlike most fields, it does not provide a value
attribute on the <input>
tags, as you don’t ever want to send back a user-entered password in your HTML:
passwordConfirmField
::
Field
Handler
Text
passwordConfirmField
=
Field
{
fieldParse
=
rawVals
_fileVals
->
case
rawVals
of
[
a
,
b
]
|
a
==
b
->
return
$
Right
$
Just
a
|
otherwise
->
return
$
Left
"Passwords don't match"
[]
->
return
$
Right
Nothing
_
->
return
$
Left
"You must enter two values"
,
fieldView
=
idAttr
nameAttr
otherAttrs
eResult
isReq
->
[
whamlet
|
<
input
id
=#
{
idAttr
}
name
=#
{
nameAttr
}
*
{
otherAttrs
}
type
=
password
>
<
div
>
Confirm:
<
input
id
=#
{
idAttr
}
-
confirm
name
=#
{
nameAttr
}
*
{
otherAttrs
}
type
=
password
>
|
]
,
fieldEnctype
=
UrlEncoded
}
getHomeR
::
Handler
Html
getHomeR
=
do
((
res
,
widget
),
enctype
)
<-
runFormGet
$
renderDivs
$
areq
passwordConfirmField
"Password"
Nothing
defaultLayout
[
whamlet
|
<
p
>
Result:
#
{
show
res
}
<
form
enctype
=#
{
enctype
}
>
^
{
widget
}
<
input
type
=
submit
value
=
"Change password"
>
|
]
Imagine you’re writing a blog hosting web app, and you want to have a form for users to enter a blog post. A blog post will consist of four pieces of information:
Title
HTML contents
User ID of the author
Publication date
We want the user to enter the first two values, but not the second two. User ID should be determined automatically by authenticating the user (a topic we haven’t covered yet), and the publication date should just be the current time. The question is, how do we keep our simple applicative form syntax, and yet pull in values that don’t come from the user?
The answer is two separate helper functions:
pure
allows us to wrap up a plain value as an applicative form value.
lift
allows us to run arbitrary Handler
actions inside an applicative form.
Let’s see an example of using these two functions:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import
Control.Applicative
import
Data.Text
(
Text
)
import
Data.Time
import
Yesod
-- We'll address this properly in Chapter 14
newtype
UserId
=
UserId
Int
deriving
Show
data
App
=
App
mkYesod
"App"
[
parseRoutes
|
/
HomeR
GET
POST
|
]
instance
Yesod
App
instance
RenderMessage
App
FormMessage
where
renderMessage
_
_
=
defaultFormMessage
type
Form
a
=
Html
->
MForm
Handler
(
FormResult
a
,
Widget
)
data
Blog
=
Blog
{
blogTitle
::
Text
,
blogContents
::
Textarea
,
blogUser
::
UserId
,
blogPosted
::
UTCTime
}
deriving
Show
form
::
UserId
->
Form
Blog
form
userId
=
renderDivs
$
Blog
<$>
areq
textField
"Title"
Nothing
<*>
areq
textareaField
"Contents"
Nothing
<*>
pure
userId
<*>
lift
(
liftIO
getCurrentTime
)
getHomeR
::
Handler
Html
getHomeR
=
do
let
userId
=
UserId
5
-- again, see Chapter 14
((
res
,
widget
),
enctype
)
<-
runFormPost
$
form
userId
defaultLayout
[
whamlet
|
<
p
>
Previous
result
:
#
{
show
res
}
<
form
method
=
post
action
=@
{
HomeR
}
enctype
=#
{
enctype
}
>
^
{
widget
}
<
input
type
=
submit
>
|
]
postHomeR
::
Handler
Html
postHomeR
=
getHomeR
main
::
IO
()
main
=
warp
3000
App
One trick we’ve introduced here is using the same handler code for both the
GET
and POST
request methods. This is enabled by the implementation of
runFormPost
, which will behave exactly like generateFormPost
in the case of
a GET
request. Using the same handler for both request methods cuts down on
some boilerplate.
Forms in Yesod are broken up into three groups. Applicative is the most common, as it provides a nice user interface with an easy-to-use API. Monadic forms give you more power, but are harder to use. Input forms are intended for when you just want to read data from the user, not generate the input widgets.
Out of the box, Yesod provides a number of different Field
s. In
order to use these in your forms, you need to indicate the kind of form and
whether the field is required or optional. The result is six helper functions:
areq
, aopt
, mreq
, mopt
, ireq
, and iopt
.
Forms have significant power available. They can automatically insert JavaScript to help you leverage nicer UI controls, such as a jQuery UI date picker. Forms are also fully i18n-ready, so you can support a global community of users. And when you have more specific needs, you can slap some validation functions onto an existing field, or write a new one from scratch.