{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | Parse forms (and query strings). module Yesod.Form ( -- * Data types GForm , FormResult (..) , Enctype (..) -- * Unwrapping functions , runFormGet , runFormPost , runFormGet' , runFormPost' -- * Field/form helpers , fieldsToTable , fieldsToPlain , module Yesod.Form.Fields -- * Template Haskell , mkToForm ) where import Yesod.Form.Core import Yesod.Form.Fields import Yesod.Form.Class 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 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 :: [FieldInfo sub y] -> GWidget sub y () fieldsToPlain = mapM_ fiInput -- | Display the label, tooltip, input code and errors in a single row of a -- table. fieldsToTable :: [FieldInfo sub y] -> GWidget sub y () fieldsToTable = 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 f = evalStateT (deform f env fe) $ IntSingle 1 -- | 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