-------------------------------------------------------------------------------- {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} -- | Internal embedding of form fields with associated functions. module Text.Digestive.Form.Internal.Field ( Field (..) , SomeField (..) , evalField , fieldMapView ) where -------------------------------------------------------------------------------- import Control.Arrow (second) import Data.Maybe (listToMaybe, mapMaybe, catMaybes) import Data.Functor ((<$>)) import Data.List (findIndex) import Data.Text (Text) -------------------------------------------------------------------------------- import Text.Digestive.Types -------------------------------------------------------------------------------- -- | A single input field. This usually maps to a single HTML @@ element. data Field v a where Singleton :: a -> Field v a Text :: Text -> Field v Text -- A list of (group name, [(identifier, (value, view))]). -- Then we have the default index in the list. -- The return value has the actual value as well as the index in the list. Choice :: [(Text, [(Text, (a, v))])] -> [Int] -> Field v [(a, Int)] Bool :: Bool -> Field v Bool File :: Field v [FilePath] -------------------------------------------------------------------------------- instance Show (Field v a) where show (Singleton _) = "Singleton _" show (Text t) = "Text " ++ show t show (Choice _ _) = "Choice _ _" show (Bool b) = "Bool " ++ show b show (File) = "File" -------------------------------------------------------------------------------- -- | Value agnostic "Field" data SomeField v = forall a. SomeField (Field v a) -------------------------------------------------------------------------------- -- | Evaluate a field to retrieve a value, using the given method and -- a list of input. evalField :: Method -- ^ Get/Post -> [FormInput] -- ^ Given input -> Field v a -- ^ Field -> a -- ^ Result evalField _ _ (Singleton x) = x evalField _ (TextInput x : _) (Text _) = x evalField _ _ (Text x) = x evalField _ ts@(TextInput _ : _) (Choice ls _) = let ls' = concat (map snd ls) in catMaybes $ map (\(TextInput x) -> (\i -> (fst $ snd $ ls' !! i, i)) <$> findIndex (isSelectedChoice x . fst) ls') ts evalField Get _ (Choice ls x) = -- this populates the default values when displaying a view let ls' = concat (map snd ls) in map (\i -> (fst $ snd $ ls' !! i, i)) x evalField Post _ (Choice _ _) = [] evalField Get _ (Bool x) = x evalField Post (TextInput x : _) (Bool _) = x == "on" evalField Post _ (Bool _) = False evalField Post xs File = mapMaybe maybeFile xs where maybeFile (FileInput x) = Just x maybeFile _ = Nothing evalField _ _ File = [] -------------------------------------------------------------------------------- -- | Map on the error message type of a Field. fieldMapView :: (v -> w) -> Field v a -> Field w a fieldMapView _ (Singleton x) = Singleton x fieldMapView _ (Text x) = Text x fieldMapView f (Choice xs i) = Choice (map (second func) xs) i where func = map (second (second f)) fieldMapView _ (Bool x) = Bool x fieldMapView _ File = File -------------------------------------------------------------------------------- -- | Determines if the choiceVal is equal to the selectedVal. Works with older -- versions that submit "foo.bar.2" rather than "2" for Choice fields. -- > isSelectedChoice "0" "0" == True -- > isSelectedChoice "foo.bar.0" "0" == True -- > isSelectedChoice "foo.bar.10" "0" == False isSelectedChoice :: Text -> Text -> Bool isSelectedChoice selectedVal choiceVal | selectedVal == choiceVal = True | otherwise = case listToMaybe (reverse $ toPath selectedVal) of Just x -> x == choiceVal _ -> False