Creation of monadic yesod form without hamlet - haskell

My goal is to write a monadic yesod form without usage of hamlet.
Let's say I have such example form:
userForm :: Maybe User -> Html -> MForm Handler (FormResult User, Widget)
userForm u extra = do
(nameR, nameV) <- mreq textField "" (userName <$> u)
(passR, passV) <- mreq passwordField "" Nothing
let userRes = User <$> nameR <*> passR
let widget = do
toWidget $ \render -> do
extra
-- fvInput nameV?
-- fvInput passV?
H.input ! type_ "submit" ! value "Submit"
return (userRes, widget)
In the case of hamlet usage it will be like that:
let widget = do
toWidget [whamlet|
^{fvInput nameV}
^{fvInput passV}
|]
But the return type of fvInput is FieldView App and I need to convert it to Html to compose with blaze well.
Perhaps, it will be easier to use Input Form for this task(i.e. implementing forms without w/hamlet), but the docs on yesodbook say it doesn't have a proper handling of the wrong input case. I tend to use monadic form here since I want a recreation of user input in the case of failed validation "for free" here. Will it work this way?

Use lift with widgetToPageContent:
userForm :: Maybe MyUser -> Html -> MForm Handler (FormResult MyUser, Widget)
userForm u extra = do
(nameR, nameV) <- mreq textField "" (myUserName <$> u)
(passR, passV) <- mreq passwordField "" Nothing
nameContents <- lift $ widgetToPageContent (fvInput nameV)
passContents <- lift $ widgetToPageContent (fvInput passV)
let userRes = MyUser <$> nameR <*> passR
let widget = do
toWidget $ \render -> do
extra
pageBody nameContents render
pageBody passContents render
H.input H.! A.type_ "submit" H.! A.value "Submit"
return (userRes, widget)

Related

How can I create a Yesod form where I can select from a dropdown list where the list is passed as an argument?

I would like to create a form in which I pass a [a] an an argument and get back an MForm Handler (FormResult a, Widget).
I have attempted to implement this using functions such as selectFieldList but have not been able to find a solution. I have not been able to find an answer anywhere on google. I have found many examples where dropdown lists have been used as individual fields in a larger form but none where the dropdown list is the entire form itself.
Edit:
I have managed to make a form that compiles. Unfortunately I am unable to tell if it works the way I would like because I cannot get it to render.
Here are the key functions I am working with (they all compile):
mkCityStringM :: [PG.DbCity] -> [(T.Text, PG.DbCity)]
mkCityStringM xs = zip (map (T.pack . showDbCity) xs) xs
data ContainCity =
ContainCity
{ getCity :: PG.DbCity
}
deriving (Show)
ambiguityForm :: [PG.DbCity] -> AForm Handler ContainCity
ambiguityForm cities = ContainCity
<$> areq (selectFieldList cityMap) "City" Nothing
where
cityMap :: [(T.Text, PG.DbCity)]
cityMap = W.mkCityStringM cities
The problem is when I try to actually use this table I run into issues. Here is code that works just fine for me:
locationForm :: Html -> MForm Handler (FormResult BasicLocation, Widget)
locationForm = renderDivs $ BasicLocation
<$> areq textField "City:" Nothing
<*> areq textField "Country:" (Just "United States")
postAmbiguityR :: [PG.DbCity] -> Handler Html
postAmbiguityR cs = do
(widget, enctype) <- generateFormPost locationForm
defaultLayout $ do
[whamlet|
<p>who cares
|]
On the other hand I have this code:
postAmbiguityR :: [PG.DbCity] -> Handler Html
postAmbiguityR cs = do
(widget, enctype) <- generateFormPost (ambiguityForm cs) --only difference
defaultLayout $ do
[whamlet|
<p> WHATEVER
|]
which throws me this error:
• Couldn't match expected type ‘blaze-markup-0.8.2.5:Text.Blaze.Internal.Markup
-> MForm (HandlerFor Base) (FormResult a0, xml0)’
with actual type ‘AForm Handler ContainCity’
• Possible cause: ‘ambiguityForm’ is applied to too many arguments
In the first argument of ‘generateFormPost’, namely
‘(ambiguityForm cs)’
In a stmt of a 'do' block:
(widget, enctype) <- generateFormPost (ambiguityForm cs)
In the expression:
do (widget, enctype) <- generateFormPost (ambiguityForm cs)
defaultLayout
$ do (do (asWidgetT . toWidget)
((blaze-markup-0.8.2.5:Text.Blaze.Internal.preEscapedText . T.pack)
"<html><header></header>
<form method="post" action="")
....)
|
132 | (widget, enctype) <- generateFormPost (ambiguityForm cs)
| ^^^^^^^^^^^^^^^^
This error message does not make sense to me. It says a possible cause is that ambiguity form is applied to too many arguments but does not seem to be the case as the form takes a list of PG.DbCity's and that is exactly what I supplied it.
The problem is that ambiguityForm is an AForm, but generateFormPost expects an MForm. To convert it to an MForm suitable for rendering, you need to use renderDivs, like you did in your locationForm example. You can either do this in the definition of ambiguityForm, which will change its type/defintion to look something like:
ambiguityForm :: [PG.DbCity] -> Html -> MForm Handler (FormResult ContainCity, Widget)
ambiguityForm cities = renderDivs $ ContainCity ...
or you can leave ambiguityForm unchanged and add renderDivs at the usage site:
postAmbiguityR :: [PG.DbCity] -> Handler Html
postAmbiguityR cs = do
(widget, enctype) <- generateFormPost (renderDivs (ambiguityForm cs))
defaultLayout $ do
[whamlet|
<p> WHATEVER
|]

How to parse yesod-form parameters into Haskell values

The code below is from the Home.hs file created by the yesod-simple scaffold.
I like to do simple string manipulation on text input but don't know how to parse it into a Text value.
How, for example, can I use toUpper on fileDescription?
I've tried using lookupPostParam
but I'm struggling with it's type signature:
lookupPostParam :: MonadHandler m => Text -> m (Maybe Text)
Home.hs
module Handler.Home where
import Import
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3)
import Text.Julius (RawJS (..))
data FileForm = FileForm
{ fileInfo :: FileInfo
, fileDescription :: Text
}
getHomeR :: Handler Html
getHomeR = do
(formWidget, formEnctype) <- generateFormPost sampleForm
let submission = Nothing :: Maybe FileForm
handlerName = "getHomeR" :: Text
defaultLayout $ do
let (commentFormId, commentTextareaId, commentListId) = commentIds
aDomId <- newIdent
setTitle "Welcome To Yesod!"
$(widgetFile "homepage")
postHomeR :: Handler Html
postHomeR = do
((result, formWidget), formEnctype) <- runFormPost sampleForm
let handlerName = "postHomeR" :: Text
submission = case result of
FormSuccess res -> Just res
_ -> Nothing
defaultLayout $ do
let (commentFormId, commentTextareaId, commentListId) = commentIds
aDomId <- newIdent
setTitle "Welcome To Yesod!"
$(widgetFile "homepage")
sampleForm :: Form FileForm
sampleForm = renderBootstrap3 BootstrapBasicForm $ FileForm
<$> fileAFormReq "Choose a file"
<*> areq textField textSettings Nothing
where textSettings = FieldSettings
{ fsLabel = "What's on the file?"
, fsTooltip = Nothing
, fsId = Nothing
, fsName = Nothing
, fsAttrs =
[ ("class", "form-control")
, ("placeholder", "File description")
]
}
commentIds :: (Text, Text, Text)
commentIds = ("js-commentForm", "js-createCommentTextarea", "js-
commentList")
This is unfortunately a fault in documentation and communication.
Given
lookupPostParam :: (MonadResource m, MonadHandler m) => Text -> m (Maybe Text)
the reader is meant to infer that m is not only a MonadResouce and a MonadHandler but also Monad. This tiny little line of code packs up a lot of intent into a very small sentence; it's a wart that so much of Haskell library usage is left implicit and subtextual. For example, to call toUpper on the Text inside this type you are meant to do this:
{-# language OverloadedStrings #-}
foo :: (MonadResource m, MonadHandler m) => m (Maybe Text)
foo = do
valueMaybe <- lookupPostParam "key"
case valueMaybe of
Just value ->
pure (toUpper value)
Nothing ->
Nothing
Note that the monad stack (MonadHandler, MonadResource) has "infected" your code. This is meant to be intentional, so as to constrain you via the type checker to only run this function in the intended Yesod environment/state machine/context/whatever.
However
You are using yesod-forms and it would be nice to do the same thing within that framework. As with lookupPostParam, we can take advantage of the monad-applicative-functor typeclasses.
We can adapt this to the Form FileForm value that you have.
sampleForm :: AForm Handler FileForm
sampleForm =
FileForm <$> fileAFormReq "Choose a file"
<*> (toUpper <$> areq textField textSettings Nothing)
I think the types of yesod-forms changed between releases. I'm copying my types off the latest version as of writing, 1.4.11.
Here we take advantage of the Monad m => Functor (AForm m) instance. Knowing that we are indeed in a monad (the Handler monad) means we can use fmap and its infixed sibling <$> on the value returned by areq textField textSettings Nothing. This allows us to lift arbitrary functions acting on Text into the AForm m stack. For example, here we went from Text -> Text to AForm Handler Text -> AForm Handler Text.
Hope that helps.

How add bootstrap to YesodForm

I need Help to add BootStrap to My YesodForm Project. Can you help me, guys ?
Thats my code. I just want add BootStrap styles to add to my html components
I already read aloot tutorials but are complexive, i am new at Haskell. I Need a simples thing just to that simple project. Thank.s
{-# LANGUAGE OverloadedStrings, TypeFamilies, QuasiQuotes,
TemplateHaskell, GADTs, FlexibleInstances,
MultiParamTypeClasses, DeriveDataTypeable,
GeneralizedNewtypeDeriving, ViewPatterns, EmptyDataDecls #-}
import Yesod
import Database.Persist.Postgresql
import Data.Text
import Text.Lucius
import Control.Monad.Logger (runStdoutLoggingT)
data Pagina = Pagina{connPool :: ConnectionPool}
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Animals json
nome Text
idade Int
deriving Show
Users json
nome Text
login Text
senha Text
deriving Show
|]
mkYesod "Pagina" [parseRoutes|
/ HomeR GET
/animal/cadastro AnimalR GET POST
/animal/checar/#AnimalsId ChecarAnimalR GET
/erro ErroR GET
/login LoginR GET POST
/usuario UsuarioR GET POST
/perfil/#UsersId PerfilR GET
/admin AdminR GET
/logout LogoutR GET
|]
instance Yesod Pagina where
authRoute _ = Just LoginR
isAuthorized LoginR _ = return Authorized
isAuthorized ErroR _ = return Authorized
isAuthorized HomeR _ = return Authorized
isAuthorized UsuarioR _ = return Authorized
isAuthorized AdminR _ = isAdmin
isAuthorized _ _ = isUser
isUser = do
mu <- lookupSession "_ID"
return $ case mu of
Nothing -> AuthenticationRequired
Just _ -> Authorized
isAdmin = do
mu <- lookupSession "_ID"
return $ case mu of
Nothing -> AuthenticationRequired
Just "admin" -> Authorized
Just _ -> Unauthorized "Acesso Restrito para Administrador"
instance YesodPersist Pagina where
type YesodPersistBackend Pagina = SqlBackend
runDB f = do
master <- getYesod
let pool = connPool master
runSqlPool f pool
type Form a = Html -> MForm Handler (FormResult a, Widget)
instance RenderMessage Pagina FormMessage where
renderMessage _ _ = defaultFormMessage
------------------------
formAnimal :: Form Animals
formAnimal = renderDivs $ Animals <$>
areq textField "Nome: " Nothing <*>
areq intField "Idade: " Nothing
formUser :: Form Users
formUser = renderDivs $ Users <$>
areq textField "Nome: " Nothing <*>
areq textField "Login: " Nothing <*>
areq passwordField "Password: " Nothing
formLogin :: Form (Text,Text)
formLogin = renderDivs $ (,) <$>
areq textField "Login: " Nothing <*>
areq passwordField "Senha: " Nothing
getAnimalR :: Handler Html
getAnimalR = do
(widget, enctype) <- generateFormPost formAnimal
defaultLayout $ do
toWidget [cassius|
label
color:blue;
|]
[whamlet|
<form .form-horizontal method=post enctype=#{enctype} action=#{AnimalR}>
^{widget}
<input type="submit" value="Cadastrar Animal">
|]
getPerfilR :: UsersId -> Handler Html
getPerfilR uid = do
user <- runDB $ get404 uid
defaultLayout $ do
toWidget $ $(luciusFile "templates/perfil.lucius")
$(whamletFile "templates/perfil.hamlet")
getUsuarioR :: Handler Html
getUsuarioR = do
(widget, enctype) <- generateFormPost formUser
defaultLayout [whamlet|
<form method=post enctype=#{enctype} action=#{UsuarioR}>
^{widget}
<input type="submit" value="Enviar">
|]
postAnimalR :: Handler Html
postAnimalR = do
((result, _), _) <- runFormPost formAnimal
case result of
FormSuccess anim -> (runDB $ insert anim) >>= \piid -> redirect (ChecarAnimalR piid)
_ -> redirect ErroR
postUsuarioR :: Handler Html
postUsuarioR = do
((result, _), _) <- runFormPost formUser
case result of
FormSuccess user -> (runDB $ insert user) >>= \piid -> redirect (PerfilR piid)
_ -> redirect ErroR
getHomeR :: Handler Html
getHomeR = defaultLayout [whamlet|Hello World!|]
addStyle :: Widget
addStyle = addStylesheetRemote "http://netdna.bootstrapcdn.com/twitter-bootstrap/2.1.0/css/bootstrap-combined.min.css"
getAdminR :: Handler Html
getAdminR = defaultLayout [whamlet|
<b><h1><font size="11"> Bem vindo ao Painel Administrativo</font></h1></b>
|]
getLoginR :: Handler Html
getLoginR = do
(widget, enctype) <- generateFormPost formLogin
defaultLayout [whamlet|
<form method=post enctype=#{enctype} action=#{LoginR}>
^{widget}
<input type="submit" value="Login">
|]
postLoginR :: Handler Html
postLoginR = do
((result, _), _) <- runFormPost formLogin
case result of
FormSuccess ("admin","eitapleuga") -> setSession "_ID" "admin" >> redirect AdminR
FormSuccess (login,senha) -> do
user <- runDB $ selectFirst [UsersLogin ==. login, UsersSenha ==. senha] []
case user of
Nothing -> redirect LoginR
Just (Entity pid u) -> setSession "_ID" (pack $ show $ fromSqlKey pid) >> redirect (PerfilR pid)
getChecarAnimalR :: AnimalsId -> Handler Html
getChecarAnimalR pid = do
animal <- runDB $ get404 pid
defaultLayout [whamlet|
<font size="10">Perfil do Pet</font><br>
<p><b> Nome do Pet:</b> #{animalsNome animal}
<p><b> Idade do Pet:</b> #{show $ animalsIdade animal} Anos
|]
getErroR :: Handler Html
getErroR = defaultLayout [whamlet|
<h1>Falha no Cadastro !</h1>
|]
getLogoutR :: Handler Html
getLogoutR = do
deleteSession "_ID"
defaultLayout [whamlet|
<h1> <b>Logout</b> efetuado com sucesso! </h1>
|]
connStr = "dbname=d4673as0stmsm7 host=ec2-54-221-225-242.compute-1.amazonaws.com user=nzjfptmglfomng password=fyYms4A9T8gkP4_Go8GswcfIiE port=5432"
main::IO()
main = runStdoutLoggingT $ withPostgresqlPool connStr 10 $ \pool -> liftIO $ do
runSqlPersistMPool (runMigration migrateAll) pool
warp 8080 (Pagina pool)
You can add it in your Handler function like this:
getUsuarioR :: Handler Html
getUsuarioR = do
(widget, enctype) <- generateFormPost formUser
defaultLayout $ do
addStylesheetRemote "http://remote-bootstrap-path.css"
[whamlet|
<form method=post enctype=#{enctype} action=#{UsuarioR}>
^{widget}
<input type="submit" value="Enviar">
|]
Also if you are new to Haskell, I would suggest you to learn some minimal Haskell before diving into Yesod.
Especially for a first project I would advise to use the scaffolded site. You can get it by following the quick start guide on the Yesod home page. Not only do you get sensible default settings, but it also has the bootstrap css already integrated.
The downside is of course that the scaffolding throws a lot of stuff and opinions at you that you might not want or not agree with. But even if you don't like the scaffolding, you can always keep one in a separate folder and just take inspiration from it and incorporate parts of it into your own site - like the integration of bootstrap or jquery.
What you need to do next is to add the appropriate classes to your html elements. There are two ways to do that, depending on the context. In your own widgets you just add them like you would any other class (with a point in front of it). If you use code that is generated, for example for forms or for yesod-table, you often have a choice between different rendering functions. For example you can render forms in two columns by using the renderTable rendering function. But there is almost always another function with a name like renderBootstrap which will render the content as plain divs with bootstrap classes.
So all in all Yesod is extremely well integrated with bootstrap. Even too much in my personal opinion. But in your case it should make your task comparatively easy after the initial setup confusion.

Get a session value with lookupSession

I try to put a session value in a variable to display it in my .hamlet but it does not focntion!
getEtatR :: Handler Html
getEtatR = do
mSessionValue <- lookupSession "myKey"
let myValue = mSessionValue :: Maybe Text
defaultLayout $ do
aDomId <- newIdent
setTitle "mon titre"
$(widgetFile "etatWidget")
I need #{myValue} to put it in my etat.hamlet
The problem is the type of myValue, which is Maybe Text. In order for a variable to show up in the template, it has to be an instance of Text.Blaze.ToMarkup.... So Text, String, or Int would all work, but "Maybe a" does not.
There are many ways to convert a "Maybe Text" to a ToMarkup. If you know for sure that the Maybe will not be a "Nothing", just strip the maybe using fromJust (imported from Data.Maybe).... But beware that if it ever does come up as a Nothing the program will crash. Similarly you could use a case statement to fill in the Nothing case, like this
myVariable = case mSessionValue of
Just x -> x
Nothing -> "<No session value>"
You can also do a quick check by converting mSessionValue to a string using show.
The following works for me....
getEtatR :: Handler Html
getEtatR = do
mSessionValue <- lookupSession "myKey"
let myValue = show mSessionValue
defaultLayout $ do
aDomId <- newIdent
setTitle "mon titre"
$(widgetFile "etatWidget")
using etatWidget.hamlet
<h1>#{myValue}
If all you want is to display the value and get it out of Maybe, you can do this directly inside the hamlet
$maybe val <- mSessionValue
<p>#{val}
$nothing
<p>No Value Set

Why does one use of toWidget work, and the other doesn't

I tried modifying a Yesod project, and came across a weird error. First I will present the working form code, and the bare-bones broken code with error message.
type PForm x = ProductConfig ->
Html ->
MForm ReScheduler ReScheduler (FormResult x, Widget)
working code next
productForm :: PForm SelectedProduct
productForm config extra = do
let pInfo' = pInfo config
aDays' = aDays config
products = (catMaybes . pNametoText) pInfo'
versions' = map consVersionPair pInfo'
productInfo <- mapM generateSelectFields versions'
(dateRes, dateView) <- mreq (selectField aDays') "placeHolder" Nothing
(mailRes, mailView) <- mreq emailField "E-Mail Address" Nothing
(noteRes, noteView) <- mreq textareaField
"Notes"
Nothing
let productVersion = reduceFormResults $
map flagSelected $
map fst productInfo
versionViews = map snd productInfo
let widget =
toWidget $(widgetFile "firmware") :: Widget
return (makeSelected productVersion dateRes mailRes noteRes, widget)
The above code works fine. Here's the broken code, followed by the error and some observations.
type RForm x = [KeyJobPair] ->
Html ->
MForm ReScheduler ReScheduler (FormResult x, Widget)
statusForm :: RForm ModData
statusForm kjPairs extra = do
-- let bPairs = buttonPairs kjPairs
-- statusPairs = map (pack . show &&& id) $
-- ([minBound .. maxBound] :: [Status])
-- (jobRes,jobView) <- mreq (radioField bPairs) "Scheduled Jobs" Nothing
-- (mailRes, mailView) <- mreq emailField "E-Mail Address" Nothing
-- (noteRes, noteView) <- mreq textareaField "Notes" Nothing
-- (statusRes, statusView) <- mreq (selectField statusPairs) "Status" Nothing
-- let widget = toWidget [hamlet|<p> testing |]
let widget = (toWidget $(widgetFile "status" )) :: Widget
return (ModData <$> undefined <*> undefined <*> undefined, widget)
Handler/Manager.hs:109:19:
No instance for (ToWidget
ReScheduler ReScheduler (GGWidget master0 m0 ()))
arising from a use of `toWidget'
Possible fix:
add an instance declaration for
(ToWidget ReScheduler ReScheduler (GGWidget master0 m0 ()))
In the expression: (toWidget ($(widgetFile "status"))) :: Widget
In an equation for `widget':
widget = (toWidget ($(widgetFile "status"))) :: Widget
In the expression:
do { let widget = ...;
return
(ModData <$> undefined <*> undefined <*> undefined, widget) }
Note the commented out hard-coded hamlet code. This compiles fine. This leads me to believe the problem lies with widgetFile, not toWidget. I noted in the Yesod blog, that sometimes widgetFile wants an explicit :: Widget type signature. I haven't been able to get this to work. Maybe this is just a syntax problem. Feedback would be welcome. In the meantime I can just use hardcoded Hamlet.
This was a syntax issue. It was a matter of proper placement of the type signature.
let widget = toWidget ($(widgetFile "status") :: Widget)
This is correct.

Resources