- Forms
- Synopsis
- Kinds of Forms
- Types
- Converting
- Create AForms
- Form with default values
- Optional fields
- Optional fields
- Default optional fields
- Validation
- More sophisticated fields
- Drop-down lists
- Uses Enum and Bounded
- Radio buttons
- Running forms
- i18n
- Monadic Forms
- A non-standard form layout
- Input forms
- Custom fields
- Summary
Forms
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 forms. Coding forms is complex; in an ideal world, we’d like a solution that addresses the following problems:
Ensure data is valid.
Marshal string data in the form submission to Haskell datatypes.
Generate HTML code for displaying the form.
Generate Javascript to do clientside 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.
Synopsis
{-# LANGUAGE QuasiQuotes, TemplateHaskell, MultiParamTypeClasses,
OverloadedStrings, TypeFamilies #-}
import Yesod
import Yesod.Form.Jquery
import Data.Time (Day)
import Data.Text (Text)
import Control.Applicative ((<$>), (<*>))
data Synopsis = Synopsis
mkYesod "Synopsis" [parseRoutes|
/ RootR GET
/person PersonR POST
|]
instance Yesod Synopsis
-- Tells our application to use the standard English messages.
-- If you want i18n, then you can supply a translating function instead.
instance RenderMessage Synopsis 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 Synopsis
-- The datatype 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 the sub and master site types, as usual.
--
-- * 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 Synopsis Synopsis (FormResult Person, Widget)
personForm = renderDivs $ Person
<$> areq textField "Name" Nothing
<*> areq (jqueryDayField def
{ jdsChangeYear = True -- give a year dropdown
, jdsYearRange = "1900:-5" -- 1900 till 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
getRootR :: Handler RepHtml
getRootR = 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.
<input type=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 RepHtml
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}
<input type=submit>
|]
main :: IO ()
main = warpDebug 3000 Synopsis
Kinds of Forms
Before jumping into the types themselves, we should begin with an overview of the different kinds of forms. There are three categories:
Applicative
These are the most commonly used (it’s what appeared in the synopsis). Applicative gives us some nice properties of letting error messages coallesce together and keep a very high-level, declarative approach. (For more information on applicative code, see the Haskell wiki.)
Monadic
A more powerful alternative to applicative. While this allows you more flexibility, it does so at the cost of being more verbose. Useful if you want to create forms that don’t fit into the standard two-column look.
Input
Used only for receiving input. Does not generate any HTML 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 individuals fields (like intField) in many different ways.
A quick note on naming conventions. Each form type has a one-letter prefix (A, M and I) which is used in a few places, such as saying 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
.
Types
The Yesod.Form.Types module declares a few types. Let’s start off with some simple helpers:
Enctype
The encoding type, either UrlEncoded
or Multipart
. This datatype declares an instance of ToHtml
, so you can use the enctype directly in Hamlet.
Env
Maps a parameter name to a list of values.
FileEnv
Maps a parameter name to the associated uploaded file.
Ints
As mentioned in the introduction, yesod-form
automatically assigns a unique name to each field. Ints
is used to keep track of the next number to assign.
FormResult
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, invalid content), or FormSuccess
if everything went smoothly.
Next we have three datatypes used for defining individual fields.
A field is a single piece of information, such as a number, a string or an email address. Fields are combined together to build forms.
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 Fields in Yesod.Form.Fields.
FieldSettings
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.)
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.
FieldView
An intermediate format containing a bunch of view information on a field. This is hardly ever used directly by the user, we’ll see more details later.
And finally, we get to the important stuff: the forms themselves. There are three types for this: MForm
is for monadic forms, AForm
for applicative and IForm
(declared in IForm) for input. MForm
is actually a type synonym for a monad stack that provides the following features:
A
Reader
monad giving us the parameters (Env
andFileEnv
), the master site argument and the list of languages the user supports. The last two are used for i18n (more on this later).A
Writer
monad keeping track of theEnctype
. A form will always beUrlEncoded
, unless there is a file input field, which will force us to use multipart instead.A
State
monad holding anInts
to keep track of the next unique name to produce.
An AForm
is pretty similar. However, there are a few major differences:
It produces a list of
FieldViews
. 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 usedrenderDivs
, which creates a bunch of div tags. Another option would berenderTable
.It does not provide a
Monad
instance. The goal ofApplicative
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 ofMonad
.
An IForm
is even simpler: it returns either a list of error messages or a result.
Converting
“But wait a minute,” you say. “You said the synopsis uses applicative forms, 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, if more 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.
Create AForms
Now that I’ve (hopefully) convinced you that in our synopsis 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 Synopsis Synopsis Car
carAForm = Car
<$> areq textField "Model" Nothing
<*> areq intField "Year" Nothing
carForm :: Html -> MForm Synopsis Synopsis (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
datatype. Perhaps 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
. So that first argument is going to determine the datatype 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 want 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.
Form with default values
carAForm :: Maybe Car -> AForm Synopsis Synopsis Car
carAForm mcar = Car
<$> areq textField "Model" (carModel <$> mcar)
<*> areq intField "Year" (carYear <$> mcar)
Optional fields
Suppose we wanted to have an optional field (like the car color). All we do instead is use the aopt
function.
Optional fields
data Car = Car
{ carModel :: Text
, carYear :: Int
, carColor :: Maybe Text
}
deriving Show
carAForm :: AForm Synopsis Synopsis Car
carAForm = Car
<$> areq textField "Model" Nothing
<*> areq intField "Year" Nothing
<*> aopt textField "Color" Nothing
And like required fields, the last argument is the optional default value. However, this has two layers of Maybe wrapping. This may seem redundant (and it is), but it makes it much easier to write code that takes an optional default form parameter, such as in the next example.
Default optional fields
data Car = Car
{ carModel :: Text
, carYear :: Int
, carColor :: Maybe Text
}
deriving Show
carAForm :: Maybe Car -> AForm Synopsis Synopsis Car
carAForm mcar = Car
<$> areq textField "Model" (carModel <$> mcar)
<*> areq intField "Year" (carYear <$> mcar)
<*> aopt textField "Color" (carColor <$> mcar)
carForm :: Html -> MForm Synopsis Synopsis (FormResult Car, Widget)
carForm = renderTable $ carAForm $ Just $ Car "Forte" 2010 $ Just "gray"
Validation
How would we make our form only accept cars created after 1990? If you remember, we said above that 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 Synopsis Synopsis 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 datatypes, 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 from the future? In order to look up the current year, we’ll need to run some IO
. For such circumstances, we’ll need checkM
:
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. However, it uses a 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.
Since 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 the Persistent chapter.
More sophisticated fields
Our color entry field is nice, but it’s not exactly user-friendly. What we really want is a drop-down list.
Drop-down lists
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 Synopsis Synopsis 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, the code above looks really repetitive; we can get the same result using the Enum and Bounded instance GHC automatically derives for us.
Uses Enum and Bounded
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 Synopsis Synopsis Car
carAForm mcar = Car
<$> areq textField "Model" (carModel <$> mcar)
<*> areq carYearField "Year" (carYear <$> mcar)
<*> aopt (selectFieldList colors) "Color" (carColor <$> mcar)
where
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 fan-out operator) to turn that into a list of pairs.
Some people prefer radio buttons to drop-down lists. Fortunately, this is just a one-word change. For example, see Radio buttons
Radio buttons
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 Synopsis Synopsis Car
carAForm mcar = Car
<$> areq textField "Model" (carModel <$> mcar)
<*> areq carYearField "Year" (carYear <$> mcar)
<*> aopt (radioFieldList colors) "Color" (carColor <$> mcar)
where
colors = map (pack . show &&& id) $ [minBound..maxBound]
Running forms
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 a FormMissing
. This automatically inserts a security token as a hidden form field to avoid 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. Also, unlike runFormPost
, it does not include CSRF protection.
runFormPostNoNonce
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
Same as generateFormPost
, but for GET
.
The return type from the first three is ((FormResult a, Widget), Enctype)
. The Widget
will already have any validation errors and previously submitted values.
i18n
There have been a few references to i18n in this chapter. The topic will get more thorough coverage in its own chapter, but since it has such a profound effect on yesod-form
, I wanted to give a brief overview. The idea behind i18n in Yesod is to have data types represent messages. Each site can have an instance of RenderMessage
for a given datatype 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
forText
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 occassionally.yesod-form
expresses all of its messages in terms of theFormMessage
datatype. Therefore, to useyesod-form
, you’ll need to have an appropriateRenderMessage
instance. A simple one that uses the default English translations would be:instance RenderMessage MyApp FormMessage where
renderMessage _ _ = defaultFormMessage
This is provided automatically by the scaffolded site.
Monadic Forms
Often times, a simple form layout is adequate, and applicative forms excel at this approach. Sometimes, however, you’ll want to have a more customized look to your form.
A non-standard form layout
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 above, we could code something like this.
{-# LANGUAGE OverloadedStrings, TypeFamilies, QuasiQuotes,
TemplateHaskell, MultiParamTypeClasses #-}
import Yesod
import Control.Applicative
import Data.Text (Text)
data MFormExample = MFormExample
mkYesod "MFormExample" [parseRoutes|
/ RootR GET
|]
instance Yesod MFormExample
instance RenderMessage MFormExample FormMessage where
renderMessage _ _ = defaultFormMessage
data Person = Person { personName :: Text, personAge :: Int }
deriving Show
personForm :: Html -> MForm MFormExample MFormExample (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: 3em;
}
|]
[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)
getRootR :: Handler RepHtml
getRootR = do
((res, widget), enctype) <- runFormGet personForm
defaultLayout [whamlet|
<p>Result: #{show res}
<form enctype=#{enctype}>
^{widget}
|]
main :: IO ()
main = warpDebug 3000 MFormExample
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 get the control 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 a FieldSettings
as its second argument. Since FieldSettings
provides an IsString
instance, the strings are essentially expanded by the compiler to:
fromString "this is not used" == FieldSettings
{ fsLabel = "this is not used"
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsClass = []
}
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 tokens to prevent CSRF attacks. If you don’t include this extra hidden field in your form, Yesod will not accept it.
Other than that, things are pretty straight-forward. We create our personRes
value by combining together the nameRes
and ageRes
values, and then return a tuple of the person and the widget. And in the getRootR
function, everything looks just like an applicative form. In fact, you could swap out our monadic form with an applicative one and the code would still work.
Input forms
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
andrunInputGet
.You use
ireq
andiopt
. These functions now only take two arguments: the field type and the name (i.e., HTMLname
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 recreate 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 explaning what you need to correct. With input forms, the user simply gets an error message.
{-# LANGUAGE OverloadedStrings, TypeFamilies, QuasiQuotes,
TemplateHaskell, MultiParamTypeClasses #-}
import Yesod
import Control.Applicative
import Data.Text (Text)
data Input = Input
mkYesod "Input" [parseRoutes|
/ RootR GET
/input InputR GET
|]
instance Yesod Input
instance RenderMessage Input FormMessage where
renderMessage _ _ = defaultFormMessage
data Person = Person { personName :: Text, personAge :: Int }
deriving Show
getRootR :: Handler RepHtml
getRootR = 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 RepHtml
getInputR = do
person <- runInputGet $ Person
<$> ireq textField "name"
<*> ireq intField "age"
defaultLayout [whamlet|<p>#{show person}|]
main :: IO ()
main = warpDebug 3000 Input
Custom fields
The fields that come built-in with Yesod will likely cover the vast majority of your form needs. But occassionally, you’ll need something more specialized. Fortunately, you can create new forms in Yesod yourself. The Field
datatype has two records: 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: shouldn’t Yesod automatically know that no information is supplied when the input list is empty? Well, no actually. 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
? Well, that’s also not the case. With grouped checkboxes and multi-select lists, you’ll have multiple widgets with the same name. We also use this trick in our example below.
The second record is fieldView
, and it renders a widget to display to the user. This function has four arguments: the id
attribute, the name
attribute, the result and a Bool
indicating if the field is required.
What did I mean by result? It’s actually an Either
, giving 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 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.
As a small example, we’ll 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 want to send back a user-entered password in your HTML ever.
passwordConfirmField :: Field sub master Text
passwordConfirmField = Field
{ fieldParse = \rawVals ->
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 _ eResult isReq -> [whamlet|
<input id=#{idAttr} name=#{nameAttr} type=password>
<div>Confirm:
<input id=#{idAttr}-confirm name=#{nameAttr} type=password>
|]
}
getRootR :: Handler RepHtml
getRootR = 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">
|]
Summary
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 when you just want to read data from the user, not generate the input widgets.
There are a number of different Field
s provided by Yesod out-of-the-box. 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 on some validation functions to an existing field, or write a new one from scratch.