203 lines
6.7 KiB
Haskell
203 lines
6.7 KiB
Haskell
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE PackageImports #-}
|
|
{-# LANGUAGE QuasiQuotes #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
-- | Parse forms (and query strings).
|
|
module Yesod.Form
|
|
( -- * Data types
|
|
GForm
|
|
, FormResult (..)
|
|
, Enctype (..)
|
|
, FormFieldSettings (..)
|
|
, Textarea (..)
|
|
-- * Type synonyms
|
|
, Form
|
|
, Formlet
|
|
, FormField
|
|
, FormletField
|
|
, FormInput
|
|
-- * Unwrapping functions
|
|
, runFormGet
|
|
, runFormPost
|
|
, runFormGet'
|
|
, runFormPost'
|
|
-- * Field/form helpers
|
|
, fieldsToTable
|
|
, fieldsToPlain
|
|
, checkForm
|
|
-- * Template Haskell
|
|
, mkToForm
|
|
-- * Re-exports
|
|
, module Yesod.Form.Fields
|
|
, module Yesod.Form.Class
|
|
) where
|
|
|
|
import Yesod.Form.Core
|
|
import Yesod.Form.Fields
|
|
import Yesod.Form.Class
|
|
import Yesod.Form.Profiles (Textarea (..))
|
|
|
|
import Text.Hamlet
|
|
import Yesod.Request
|
|
import Yesod.Handler
|
|
import Control.Applicative hiding (optional)
|
|
import Data.Maybe (fromMaybe, mapMaybe)
|
|
import "transformers" Control.Monad.IO.Class
|
|
import Control.Monad ((<=<))
|
|
import Control.Monad.Trans.State
|
|
import Control.Monad.Trans.Reader
|
|
import Language.Haskell.TH.Syntax
|
|
import Database.Persist.Base (EntityDef (..))
|
|
import Data.Char (toUpper, isUpper)
|
|
import Yesod.Widget
|
|
import Control.Arrow ((&&&))
|
|
import Data.List (group, sort)
|
|
|
|
-- | Display only the actual input widget code, without any decoration.
|
|
fieldsToPlain :: FormField sub y a -> Form sub y a
|
|
fieldsToPlain = mapFormXml $ mapM_ fiInput
|
|
|
|
-- | Display the label, tooltip, input code and errors in a single row of a
|
|
-- table.
|
|
fieldsToTable :: FormField sub y a -> Form sub y a
|
|
fieldsToTable = mapFormXml $ mapM_ go
|
|
where
|
|
go fi = do
|
|
wrapWidget (fiInput fi) $ \w -> [$hamlet|
|
|
%tr
|
|
%td
|
|
%label!for=$fiIdent.fi$ $fiLabel.fi$
|
|
.tooltip $fiTooltip.fi$
|
|
%td
|
|
^w^
|
|
$maybe fiErrors.fi err
|
|
%td.errors $err$
|
|
|]
|
|
|
|
runFormGeneric :: Env
|
|
-> FileEnv
|
|
-> GForm sub y xml a
|
|
-> GHandler sub y (FormResult a, xml, Enctype)
|
|
runFormGeneric env fe (GForm f) =
|
|
runReaderT (runReaderT (evalStateT f $ IntSingle 1) env) fe
|
|
|
|
-- | Run a form against POST parameters.
|
|
runFormPost :: GForm sub y xml a
|
|
-> GHandler sub y (FormResult a, xml, Enctype)
|
|
runFormPost f = do
|
|
rr <- getRequest
|
|
(pp, files) <- liftIO $ reqRequestBody rr
|
|
runFormGeneric pp files f
|
|
|
|
-- | Run a form against POST parameters, disregarding the resulting HTML and
|
|
-- returning an error response on invalid input.
|
|
runFormPost' :: GForm sub y xml a -> GHandler sub y a
|
|
runFormPost' = helper <=< runFormPost
|
|
|
|
-- | Run a form against GET parameters, disregarding the resulting HTML and
|
|
-- returning an error response on invalid input.
|
|
runFormGet' :: GForm sub y xml a -> GHandler sub y a
|
|
runFormGet' = helper <=< runFormGet
|
|
|
|
helper :: (FormResult a, b, c) -> GHandler sub y a
|
|
helper (FormSuccess a, _, _) = return a
|
|
helper (FormFailure e, _, _) = invalidArgs e
|
|
helper (FormMissing, _, _) = invalidArgs ["No input found"]
|
|
|
|
-- | Run a form against GET parameters.
|
|
runFormGet :: GForm sub y xml a
|
|
-> GHandler sub y (FormResult a, xml, Enctype)
|
|
runFormGet f = do
|
|
gs <- reqGetParams `fmap` getRequest
|
|
runFormGeneric gs [] f
|
|
|
|
-- | Create 'ToForm' instances for the entities given. In addition to regular 'EntityDef' attributes understood by persistent, it also understands label= and tooltip=.
|
|
mkToForm :: [EntityDef] -> Q [Dec]
|
|
mkToForm = mapM derive
|
|
where
|
|
afterPeriod s =
|
|
case dropWhile (/= '.') s of
|
|
('.':t) -> t
|
|
_ -> s
|
|
beforePeriod s =
|
|
case break (== '.') s of
|
|
(t, '.':_) -> Just t
|
|
_ -> Nothing
|
|
getSuperclass (_, _, z) = getTFF' z >>= beforePeriod
|
|
getTFF (_, _, z) = maybe "toFormField" afterPeriod $ getTFF' z
|
|
getTFF' [] = Nothing
|
|
getTFF' (('t':'o':'F':'o':'r':'m':'F':'i':'e':'l':'d':'=':x):_) = Just x
|
|
getTFF' (_:x) = getTFF' x
|
|
getLabel (x, _, z) = fromMaybe (toLabel x) $ getLabel' z
|
|
getLabel' [] = Nothing
|
|
getLabel' (('l':'a':'b':'e':'l':'=':x):_) = Just x
|
|
getLabel' (_:x) = getLabel' x
|
|
getTooltip (_, _, z) = fromMaybe "" $ getTooltip' z
|
|
getTooltip' (('t':'o':'o':'l':'t':'i':'p':'=':x):_) = Just x
|
|
getTooltip' (_:x) = getTooltip' x
|
|
getTooltip' [] = Nothing
|
|
getId (_, _, z) = fromMaybe "" $ getId' z
|
|
getId' (('i':'d':'=':x):_) = Just x
|
|
getId' (_:x) = getId' x
|
|
getId' [] = Nothing
|
|
getName (_, _, z) = fromMaybe "" $ getName' z
|
|
getName' (('n':'a':'m':'e':'=':x):_) = Just x
|
|
getName' (_:x) = getName' x
|
|
getName' [] = Nothing
|
|
derive :: EntityDef -> Q Dec
|
|
derive t = do
|
|
let cols = map ((getId &&& getName) &&& ((getLabel &&& getTooltip) &&& getTFF)) $ entityColumns t
|
|
ap <- [|(<*>)|]
|
|
just <- [|pure|]
|
|
nothing <- [|Nothing|]
|
|
let just' = just `AppE` ConE (mkName $ entityName t)
|
|
string' <- [|string|]
|
|
mfx <- [|mapFormXml|]
|
|
ftt <- [|fieldsToTable|]
|
|
ffs' <- [|FormFieldSettings|]
|
|
let stm "" = nothing
|
|
stm x = just `AppE` LitE (StringL x)
|
|
let go_ = go ap just' ffs' stm string' mfx ftt
|
|
let c1 = Clause [ ConP (mkName "Nothing") []
|
|
]
|
|
(NormalB $ go_ $ zip cols $ map (const nothing) cols)
|
|
[]
|
|
xs <- mapM (const $ newName "x") cols
|
|
let xs' = map (AppE just . VarE) xs
|
|
let c2 = Clause [ ConP (mkName "Just") [ConP (mkName $ entityName t)
|
|
$ map VarP xs]]
|
|
(NormalB $ go_ $ zip cols xs')
|
|
[]
|
|
let y = mkName "y"
|
|
let ctx = map (\x -> ClassP (mkName x) [VarT y])
|
|
$ map head $ group $ sort
|
|
$ mapMaybe getSuperclass
|
|
$ entityColumns t
|
|
return $ InstanceD ctx ( ConT ''ToForm
|
|
`AppT` ConT (mkName $ entityName t)
|
|
`AppT` VarT y)
|
|
[FunD (mkName "toForm") [c1, c2]]
|
|
go ap just' ffs' stm string' mfx ftt a =
|
|
let x = foldl (ap' ap) just' $ map (go' ffs' stm string') a
|
|
in mfx `AppE` ftt `AppE` x
|
|
go' ffs' stm string' (((theId, name), ((label, tooltip), tff)), ex) =
|
|
let label' = string' `AppE` LitE (StringL label)
|
|
tooltip' = string' `AppE` LitE (StringL tooltip)
|
|
ffs = ffs' `AppE`
|
|
label' `AppE`
|
|
tooltip' `AppE`
|
|
(stm theId) `AppE`
|
|
(stm name)
|
|
in VarE (mkName tff) `AppE` ffs `AppE` ex
|
|
ap' ap x y = InfixE (Just x) ap (Just y)
|
|
|
|
toLabel :: String -> String
|
|
toLabel "" = ""
|
|
toLabel (x:rest) = toUpper x : go rest
|
|
where
|
|
go "" = ""
|
|
go (c:cs)
|
|
| isUpper c = ' ' : c : go cs
|
|
| otherwise = c : go cs
|