Recently I stumbled across the following problem:
Using yesod
I wanted to
FormFailure
redirect the browser to the previous pageAs far as I'm concerned this is what POST/Redirect/GET
is all about.
While the points <1-3> required a simple and straightforward implementation, I found it impossible to achieve point <4>!
The yesod-form package automatically handles this issue, but doesn't allow any redirects between form parsing <2> and error handling <3,4> as I would like it to be.
You suggest I should serialize the submitted data and somehow inject it into the form after the redirect. This leads to more detailed questions:
How to get the data to serialize?
I know I could use runRequestBody
:: GHandler s m RequestBodyContents
, but which are the relevant information (The name
s of the fields are generated automatically)?
How to inject the data into the form?
If you look at the type of e.g. aopt
aopt :: Field sub master a -> FieldSettings master -> Maybe (Maybe a) -> AForm sub master (Maybe a)
you'll see that it requires the default value to be of the same type as the Field
, so it's not possible to re-insert user supplied data which might not parse correctly.
Example: The user types an 'A' into an intField
. Now I want to be able to display the 'A' in the same field after a redirect, but the API doesn't allow me to.
How should I deal with this problem?
I personally think it's acceptable to return a filled-in form with a POST request, which is what the yesod-form API is optimized for. If you want to force a redirect on form submission failures as well, you'll need to serialize the submitted data and store it somewhere, e.g.:
Old question, but I needed this today, so might as well post it for others running into the same issue.
Basically, as Michael suggests, we can serialize the data to the session. Doing this is tricky, plus getting it into a form is even trickier. I had to rip postEnv
and postHelper
from Yesod.Form.Functions
since they are not exported but are needed to do this.
You can then use setLastInvalidPost
in your handler before a redirect then use generateFormFromLastPost
in the destination handler.
Note that it would probably be better to use something like Data.Serialize
for serialization; however, Show
/Read
instances were good enough for my needs (and much simpler).
Here's the good stuff. If you want a full working snippet, you can check out my gist.
-- Create a form from last post data in the session if exists, otherwise create a blank form.
generateFormFromLastPost :: (RenderMessage (HandlerSite m) FormMessage, MonadHandler m) =>
(Markup -> MForm m (FormResult a, xml)) -> m (xml, Enctype)
generateFormFromLastPost form = do
env <- getLastInvalidPost
case env of
Nothing -> generateFormPost form
Just _ -> first snd <$> postHelper form env
lastInvalidPostSessionKey :: Text
lastInvalidPostSessionKey = "lastInvalidPost"
-- Sets the post data retreived from postEnv, ignoring the FileEnv.
setLastInvalidPost :: MonadHandler m => Maybe (Env, FileEnv) -> m ()
setLastInvalidPost Nothing = return ()
setLastInvalidPost (Just (env, _)) = sessionSetter lastInvalidPostSessionKey env
-- Retrieves the previous post data to be passed to postHelper.
getLastInvalidPost :: MonadHandler m => m (Maybe (Env, FileEnv))
getLastInvalidPost = do
result <- sessionGetter lastInvalidPostSessionKey
return $ case result of
Nothing -> Nothing
Just env -> Just (env, Map.fromList [])
sessionSetter :: (MonadHandler m, Show a) => Text -> a -> m ()
sessionSetter key = setSession key . pack . show
sessionGetter :: (MonadHandler m, Read b) => Text -> m (Maybe b)
sessionGetter key = do
m <- lookupSession key
return $ readMaybe . unpack =<< m
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With