More Samples
The code for these samples is in code/df-samples
. The app in df-samples is a copy of the Twitter app, with various forms, routes and templates added.
Adding Splices
digestiveSplices
digestiveSplices
are the splices that comes from digestive-functors-heist
. It takes a View T.Text (for example the one in tweetFormHandler
in code/df-one/src/Twitter.hs
)
bindDigestiveSplices
bindDigestiveSplices
is used very similar to heist's bindSplices
. As seen in code/df-one/src/Twitter.hs
from earlier in this chapter:
tweetFormHandler :: Handler App App ()
tweetFormHandler = do
(view, result) <- runForm "tweet" tweetForm
case result of
Just x -> writeText $ T.pack $ show x
Nothing -> heistLocal (bindDigestiveSplices view) $ render "tweetform"
digestiveSplices and custom splices
bindDigestiveSplices
returns a heistState
, just like bindSplices
does. This means that if we need to add custom splices and digestiveSplices
to render a template, we can do it like this:
Provided we have these imports:
import qualified Heist.Interpreted as I
import Snap.Snaplets.Heist
we can change this from code/df-one/src/Twitter.hs
heistLocal (bindDigestiveSplices view) $ render "tweetform"
to this
heistLocal bSplices $ render "tweetform"
where mysplices = [("thing", I.textSplice "what")
,("otherthing", I.textSplice "other")]
bSplices = (I.bindSplices mysplices . bindDigestiveSplices view)
The key part is replacing bindDigestiveSplices view
with:
I.bindSplices mysplices . bindDigestiveSplices view
where mysplices
is a list of simple textSplice
s.
Forms
All of these return Formlet
s that can be used when creating forms.
text
text
defines a text form and takes an optional default value (Just "some text"
or Nothing
)
"username" .: text Nothing
and as seen in code/df-one/src/Twitter.hs
:
"username" .: check userErrMsg isNotEmpty (text Nothing)
string
Same as text
, but works on the String
type.
stringRead
stringRead
is used for parseable and serializable values. In our Tweet example, we used Int
. We can check the documentation for Int to see that in the Instances
list there are instances for Read Int
and Show Int
. Read
and Show
are the instances that make a value "parseable and serializable".
It takes an error message and possibly a default value (either Just 5
or Nothing
in our case).
"timestamp" .: stringRead "My Error Message" Nothing
choice
A choice
form can be used to restrict options, such as with a select tag. It takes a list of value, identifier pairs and an optional default value. From code/df-samples/Twitter.hs
:
-- Data Type with two possible values.
data Thing = ThingOne | ThingTwo
deriving (Show, Eq)
-- Form for data type using choice. "t1" and "t2"
-- will render in the dfInputSelect box
thingForm :: (Monad m) => Form T.Text m Thing
thingForm = "thething" .: choice [(ThingOne, "t1"),
(ThingTwo, "t2")] Nothing
Showing the flexibility of Digestive Functors, we have two template choices set up to render our choices. The first is at localhost:8000/thing
, which renders a select box, the second is at localhost:8000/thingradio
, which renders a set of radio buttons. Here are the relevant parts of both templates:
<dfLabel ref="thething">The Thing: </dfLabel>
<dfInputSelect ref="thething" />
and
<dfLabel ref="thething">The Thing: </dfLabel>
<dfInputRadio ref="thething" />
bool
bool
is for Boolean values, like true and false and of course, takes an optional default value (Such as Just True
or Nothing
).
data Runner = Runner {
isRunner :: Bool,
name :: T.Text
} deriving (Show)
runnerForm :: (Monad m) => Form T.Text m Runner
runnerForm = Runner
<$> "isrunner" .: bool Nothing
<*> "name" .: text Nothing
and the relevant templates in which we use a checkbox:
<dfLabel ref="name">Name: </dfLabel>
<dfInputText ref="name" />
<br>
<dfLabel ref="isrunner">Are you a Runner? </dfLabel>
<dfInputCheckbox ref="isrunner"/>
<br>
Testing Optional Values
optionalText
optionalText
functions the same way as text
with one difference; It accepts Maybe
values (that is, it's for fields that don't need to be filled out).
data MRunner = MRunner {
isMRunner :: Bool,
mName :: Maybe T.Text
} deriving (Show)
mRunnerForm :: (Monad m) => Form T.Text m MRunner
mRunnerForm = MRunner
<$> "isrunner" .: bool Nothing
<*> "name" .: optionalText Nothing
This form can use the same template as a text
field. For example, here's the template from the bool
example which had a text
name:
<dfLabel ref="name">Name: </dfLabel>
<dfInputText ref="name" />
<br>
<dfLabel ref="isrunner">Are you a Runner? </dfLabel>
<dfInputCheckbox ref="isrunner"/>
<br>
Two potential successful form responses:
MRunner {isMRunner = True, mName = Just "Chris"}
MRunner {isMRunner = True, mName = Nothing}
Digestive Splices
The Digestive Splices are what are used to construct templates. If you are familiar with html input tags already, most of them are named the same way as the html tag they generate. Some examples are given here but a complete list can be found in the Heist docs on Hackage.
dfInput
Generates an <input>
tag with the desired type.
<dfInput type="date" ref="date" />
dfInputText
This is how most of the other input tags are used. Define a ref
attribute so that we can access it in our form validation. This ref will be translated to a namespaced equivalent (Something like myform.name
in this example).
<dfInputText ref="username" />
becomes
<input type="text" id="tweet.username" name="tweet.username" value="">
A More Complicated Example
<dfInput ref="timestamp" type="number" min="0" step="1" pattern="\d+" />
becomes
<input type="number" min="0" step="1"
pattern="\d+" id="tweet.timestamp"
name="tweet.timestamp" value="">
Similar Splices
- dfInputTextArea
- dfInputPassword
- dfInputHidden
- dfInputSelect
- dfInputRadio
- dfInputCheckbox
dfInputSubmit
<dfInputSubmit value="Submit" />
becomes
<input value="Submit" type="submit">
dfLabel
<dfLabel ref="timestamp">Timestamp: </dfLabel>
becomes
<label for="tweet.timestamp">Timestamp: </label>
dfForm
dfForm
is the equivalent of a <form>
tag.
<dfForm action="/tweet">
becomes
<form action="/tweet" method="POST" enctype="application/x-www-form-urlencoded">
dfErrorList
dfErrorList
renders the errors for a specific input on the form.
<dfErrorList ref="name">
dfChildErrorList
dfChildErrorList
renders as a list of all of the errors in the form.
<dfChildErrorList ref="" />
could render as
<ul>
<li>Username can not be empty</li>
<li>timestamp must be an Int</li>
<li>Tweet can not be empty</li>
</ul>