Error when using options fields in Yesod subsite form - haskell

I'm trying to use a selectFieldList inside a subsite form but I get the following error:
Couldn't match type 'IO' with 'HanderT master IO'
I'm running into this problem when using the following snippets, where the subsite is named TestSub (this subsite is separated from the master site):
Types:
type TestHandler a = forall master. Yesod master
=> HandlerT TestSub (HandlerT master IO) a
type Form a = forall master. Yesod master
=> Html -> MForm (HandlerT TestSub (HandlerT master IO)) (FormResult a, WidgetT TestSub IO ())
Form & handler:
testForm :: Form (Text, Int)
testForm = renderBootstrap3 BootstrapBasicForm $ (,)
<$> areq textField (bfs MsgText) Nothing
<*> areq (selectFieldList [(MsgFirst, 1), (MsgSecond, 2)]) (bfs MsgSelect) Nothing
getTestHome :: TestHandler Html
getTestHome = do
(formWidget, _) <- generateFormPost testForm
defaultLayoutSub $ do
setTitleI MsgTest
[whamlet|^{formWidget}|]
when the select field is replaced with for instance an intField the form and handler work as expected. While looking for the selectFieldList on Hoogle I found that options fields (select, radio, checkbox) have a different signature (displayed below) then the "normal" fields. I suspect this difference to be the problem but haven't found a work-around without having to implement the option fields all over.
Options field signature:
selectFieldList :: (Eq a, RenderMessage site FormMessage, RenderMessage site msg)
=> [(msg, a)]
-> Field (HandlerT site IO) a
Normal field signature:
intField :: (Monad m, Integral i, RenderMessage (HandlerSite m) FormMessage)
=> Field m i
Is there a way to get the options fields to work in a subsite context, without reimplementing them?

It's usually best to run your forms in the master site, not the subsite, by calling lift. You'll also need to modify your type synonyms a bit to match, but the basic idea is to replace:
(formWidget, _) <- generateFormPost testForm
with
(formWidget, _) <- lift $ generateFormPost testForm
EDIT
I still recommend the above approach. However, to get the alternate that you're asking for, change your type synonym to:
type Form a =
Html -> MForm (HandlerT HelloSub IO) (FormResult a, WidgetT HelloSub IO ())
and then use liftHandlerT:
liftHandlerT $ generateFormPost testForm
Keep in mind that this isn't how subsites are designed to be used, so you'll likely end up with some more friction as you keep going.
Regarding master translations: you can definitely leverage them, you just put in a constraint along the lines of RenderMessage master MessageDataType. That's what's used for FormMessage all over the place.
EDIT2
One more incantation you may find useful:
defaultLayoutSub $ liftWidgetT widget

Related

Yesod Subsite Function Handler

I have an exposed function (a simple function with one Text argument, that performs IO operations and then returns a bool) in my subsite. But every time i call it from my main site boolVal <- Sub.getVal "abc" it throws the following error:
Couldn't match type ‘Sub’ with ‘App’
Expected type: HandlerT App IO Bool
Actual type: HandlerT Sub (HandlerT master0 IO) Bool
subsite code snippet
type SubHandler a = forall master. Yesod master => HandlerT Sub (HandlerT
master IO) a
getVal :: Text -> SubHandler Bool
getVal name = do
-- some operations including IO
return False
main site code snippet
import Sub
getHomeR :: Handler Html
getHomeR = do
boolVal <- Sub.getVal "abc"
defaultLayout $ do
$(widgetFile "home/index")
The subsite routes are working fine and i foolishly presumed the SubHandler would have registered with the main App, magically, like most things in haskell ha
thank you
FYI i would really appreciate a technical answer to this, and not just "the types don't match", albeit my understanding of classes and instances declarations is not all that great atm - work in progress :)

Yesod fvInput can't match 'App' with 'Auth'

I'm trying to write a custom Yesod registration form. The problem I'm having is that when it gets to fvInput it seems like it's using App instead of Auth. I'm not quite sure how this should be handled and I can't seem to find the terminology for it. I've tried lifting in various ways within the form and I can only get it to throw different errors. Also, the only time this error is thrown is if I have the fvInput line, but if I remove that no error is thrown and it compiles correctly.
Code:
registrationForm :: Html -> MForm (HandlerT Auth (HandlerT App IO)) (FormResult UserForm, Widget)
registrationForm extra = do
(emailRes, emailView) <- mreq textField "" Nothing
let userRes = UserForm <$> emailRes
let widget = do
[whamlet|
#{extra}
^{fvInput emailView}
<input type=Submit value="Registration">
|]
return (userRes, widget)
Error:
Foundation.hs:169:30:
Couldn't match type ‘App’ with ‘Auth’
In the second argument of ‘(GHC.Base..)’, namely ‘toWidget’
In the expression: asWidgetT GHC.Base.. toWidget
In a stmt of a 'do' block:
(asWidgetT GHC.Base.. toWidget) (fvInput emailView)
Thank you in advance for any help!
Edit
Error with a lift before the mreq:
Foundation.hs:166:49:
Couldn't match type ‘HandlerT Auth (HandlerT App IO)’
with ‘transformers-0.4.2.0:Control.Monad.Trans.RWS.Lazy.RWST
(Maybe (Env, FileEnv), HandlerSite m0, [Lang]) Enctype Ints m0’
Expected type: HandlerT
Auth (HandlerT App IO) (FormResult Text, FieldView App)
Actual type: MForm m0 (FormResult Text, FieldView App)
In the second argument of ‘($)’, namely ‘mreq textField "" Nothing’
In a stmt of a 'do' block:
(emailRes, emailView) <- lift $ mreq textField "" Nothing
Your returned Widget type is actually:
WidgetT App IO ()
Thus there's a mismatch between the Widget (which lives in just App) and the emailView (which lives in the lifted HandlerT Auth (HandlerT App IO) monad).
To solve this problem:
Change you type signature to registrationForm :: Html -> MForm (HandlerT App IO) (FormResult UserForm, Widget)
At the usage site, you'll need to employ a lift most likely, but after you've already called the appropriate run function for the form

use runDB in a SubSite Yesod

i want create a post method in a SubSite to create a new entity, i have this AForm
demoForm :: RenderMessage master FormMessage => Maybe Demo -> AForm (HandlerT master IO) Demo
demoForm demo = Demo
<$> areq textField (fieldSettingsLabel ("fieldone"::T.Text)) (demoFieldOne <$> demo)
<*> areq intField (fieldSettingsLabel ("fieldone"::T.Text)) (demoFieldTwo <$> demo)
<*> areq boolField (fieldSettingsLabel ("fieldThree"::T.Text)) (demoFieldThree <$> demo)
<*> areq dayField (fieldSettingsLabel ("fieldFour"::T.Text)) (demoFieldFour <$> demo)
and the Post method:
postDemoNewR :: (Yesod master,RenderMessage master FormMessage) => HandlerT DemoCrud (HandlerT master IO) Html
postDemoNewR = do
tp <- getRouteToParent
((result,widget), encoding) <- lift $ runFormPost $ renderBootstrap3 BootstrapBasicForm $ demoForm Nothing
case result of
FormSuccess demo -> do
_ <- lift $ runDB $ insert demo
redirect DemoNewR
_ -> lift $ defaultLayout $ do
let actionR = DemoNewR
[whamlet|
<form method=post action=#{tp DemoNewR} encType=#{encoding}>
^{widget}
<button .btn .btn-default> default text create
|]
but have the following error
Could not deduce (YesodPersistBackend master
~ persistent-2.1.3:Database.Persist.Sql.Types.SqlBackend)
from the context (Yesod master, RenderMessage master FormMessage)
bound by the type signature for
postDemoNewR :: (Yesod master, RenderMessage master FormMessage) =>
I thing that I need add YesodPersist but i'm not sure how
You need to add the following constraint to the postDemoNewR declaration:
YesodPersist master => YesodPersistBackend master ~ SqlBackend => …
The first constraint tells master must have persistent abilities while the second constraint tells the backend used for persistent should be an SQL backend.
You can find something similar in this other question

How can extract Html from a WidgetT (HandlerSite LiteHandler) IO () value in Yesod

Shalom! I have the following code:
widget_member :: Html -> MForm LiteHandler (FormResult Member, WidgetT (HandlerSite LiteHandler) IO ())
widget_member = renderDivs input_member
handler_home :: LiteHandler Html
handler_home = do
(widget, e) <- generateFormPost widget_member
Can anyone show how I can extract the Html from widget so that handler_home can return it?
You probably don't want to actually extract the Html; you probably want to call the defaultLayout function. Most examples of using forms take this approach.

Yesod generateFormPost - No instance for (RenderMessage master0 FormMessage)

I tried to follow this screencast by Michael Snoyman http://vimeo.com/39646807. However, it seems changes to the i18n causes that code to fail. I can't find any information on how to solve this problem on a scaffolded site and I can't quite make sense of the information given here http://www.yesodweb.com/book/internationalization.
This is the error i get, referring to code in Home.hs:
No instance for (RenderMessage master0 FormMessage)
arising from a use of `generateFormPost'
Possible fix:
add an instance declaration for (RenderMessage master0 FormMessage)
In a stmt of a 'do' block:
(formWidget, enctype) <- generateFormPost noteForm
In the expression:
do { (formWidget, enctype) <- generateFormPost noteForm;
defaultLayout ($(widgetFile "notes")) }
In an equation for `getNotesR':
getNotesR
= do { (formWidget, enctype) <- generateFormPost noteForm;
defaultLayout ($(widgetFile "notes")) }
The information seems pretty clear, the problem is I can't figure out how to add an instance declaration for (RenderMessage master0 FormMessage).
Here's the code I added to Home.hs
noteForm = renderBootstrap $ Note
<$> areq textField "Title" Nothing
<*> areq textField "Content" Nothing
getNotesR = do
(formWidget, enctype) <- generateFormPost noteForm
defaultLayout $(widgetFile "notes")
postNotesR = return ()
getNoteR noteId = return ()
Ant the following is from templates/notes.hamlet
<form method=post enctype=#{enctype}>
^{formWidget}
<input type=submit>
As a general proposition, when you see something like this:
No instance for (RenderMessage master0 FormMessage)
Bear in mind that master0 (or anything like it starting with a lowercase letter) is a free type variable that has not been instantiated to a concrete type. You can mentally replace it with a if that helps. So now we see that the message says there's no generic RenderMessage instance that is uniquely determined by FormMessage in the second parameter and an arbitrary type in the first parameter.
therefore, the usual way to fix this is to figure out what you want to instantiate the free type to, and to provide a type signature or other hint to fix it to that instantiation.
In this case, the type signature suggested by Michael Snoyman noteForm :: Form Note serves this purpose.
What about adding this code ?
instance RenderMessage [The name of your Yesod instance] FormMessage where
renderMessage _ _ = defaultFormMessage
See http://www.yesodweb.com/book/internationalization.

Resources