haskellyesodyesod-forms

Error when using options fields in Yesod subsite form


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?


Solution

  • 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