fileField unabled

Boils down to two changes:

* fieldParse takes a list of FileInfos
* fieldEnctype added
This commit is contained in:
Michael Snoyman 2012-11-09 08:58:01 +02:00
parent b15faa3cf8
commit 2ea1bcb5c8
7 changed files with 76 additions and 32 deletions

View File

@ -27,6 +27,7 @@ module Yesod.Form.Fields
, Textarea (..) , Textarea (..)
, boolField , boolField
, checkBoxField , checkBoxField
, fileField
-- * File 'AForm's -- * File 'AForm's
, fileAFormReq , fileAFormReq
, fileAFormOpt , fileAFormOpt
@ -104,6 +105,7 @@ intField = Field
$newline never $newline never
<input id="#{theId}" name="#{name}" *{attrs} type="number" :isReq:required="" value="#{showVal val}"> <input id="#{theId}" name="#{name}" *{attrs} type="number" :isReq:required="" value="#{showVal val}">
|] |]
, fieldEnctype = UrlEncoded
} }
where where
showVal = either id (pack . showI) showVal = either id (pack . showI)
@ -120,6 +122,7 @@ doubleField = Field
$newline never $newline never
<input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required="" value="#{showVal val}"> <input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required="" value="#{showVal val}">
|] |]
, fieldEnctype = UrlEncoded
} }
where showVal = either id (pack . show) where showVal = either id (pack . show)
@ -130,6 +133,7 @@ dayField = Field
$newline never $newline never
<input id="#{theId}" name="#{name}" *{attrs} type="date" :isReq:required="" value="#{showVal val}"> <input id="#{theId}" name="#{name}" *{attrs} type="date" :isReq:required="" value="#{showVal val}">
|] |]
, fieldEnctype = UrlEncoded
} }
where showVal = either id (pack . show) where showVal = either id (pack . show)
@ -140,6 +144,7 @@ timeField = Field
$newline never $newline never
<input id="#{theId}" name="#{name}" *{attrs} :isReq:required="" value="#{showVal val}"> <input id="#{theId}" name="#{name}" *{attrs} :isReq:required="" value="#{showVal val}">
|] |]
, fieldEnctype = UrlEncoded
} }
where where
showVal = either id (pack . show . roundFullSeconds) showVal = either id (pack . show . roundFullSeconds)
@ -156,6 +161,7 @@ $newline never
$# FIXME: There was a class="html" attribute, for what purpose? $# FIXME: There was a class="html" attribute, for what purpose?
<textarea id="#{theId}" name="#{name}" *{attrs}>#{showVal val} <textarea id="#{theId}" name="#{name}" *{attrs}>#{showVal val}
|] |]
, fieldEnctype = UrlEncoded
} }
where showVal = either id (pack . renderHtml) where showVal = either id (pack . renderHtml)
@ -184,6 +190,7 @@ textareaField = Field
$newline never $newline never
<textarea id="#{theId}" name="#{name}" *{attrs}>#{either id unTextarea val} <textarea id="#{theId}" name="#{name}" *{attrs}>#{either id unTextarea val}
|] |]
, fieldEnctype = UrlEncoded
} }
hiddenField :: (PathPiece p, RenderMessage master FormMessage) hiddenField :: (PathPiece p, RenderMessage master FormMessage)
@ -194,6 +201,7 @@ hiddenField = Field
$newline never $newline never
<input type="hidden" id="#{theId}" name="#{name}" *{attrs} value="#{either id toPathPiece val}"> <input type="hidden" id="#{theId}" name="#{name}" *{attrs} value="#{either id toPathPiece val}">
|] |]
, fieldEnctype = UrlEncoded
} }
textField :: RenderMessage master FormMessage => Field sub master Text textField :: RenderMessage master FormMessage => Field sub master Text
@ -204,6 +212,7 @@ textField = Field
$newline never $newline never
<input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required value="#{either id id val}"> <input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required value="#{either id id val}">
|] |]
, fieldEnctype = UrlEncoded
} }
passwordField :: RenderMessage master FormMessage => Field sub master Text passwordField :: RenderMessage master FormMessage => Field sub master Text
@ -213,6 +222,7 @@ passwordField = Field
$newline never $newline never
<input id="#{theId}" name="#{name}" *{attrs} type="password" :isReq:required="" value="#{either id id val}"> <input id="#{theId}" name="#{name}" *{attrs} type="password" :isReq:required="" value="#{either id id val}">
|] |]
, fieldEnctype = UrlEncoded
} }
readMay :: Read a => String -> Maybe a readMay :: Read a => String -> Maybe a
@ -286,6 +296,7 @@ emailField = Field
$newline never $newline never
<input id="#{theId}" name="#{name}" *{attrs} type="email" :isReq:required="" value="#{either id id val}"> <input id="#{theId}" name="#{name}" *{attrs} type="email" :isReq:required="" value="#{either id id val}">
|] |]
, fieldEnctype = UrlEncoded
} }
type AutoFocus = Bool type AutoFocus = Bool
@ -307,6 +318,7 @@ $newline never
#{theId} #{theId}
-webkit-appearance: textfield -webkit-appearance: textfield
|] |]
, fieldEnctype = UrlEncoded
} }
urlField :: RenderMessage master FormMessage => Field sub master Text urlField :: RenderMessage master FormMessage => Field sub master Text
@ -320,6 +332,7 @@ urlField = Field
$newline never $newline never
<input ##{theId} name=#{name} *{attrs} type=url :isReq:required value=#{either id id val}> <input ##{theId} name=#{name} *{attrs} type=url :isReq:required value=#{either id id val}>
|] |]
, fieldEnctype = UrlEncoded
} }
selectFieldList :: (Eq a, RenderMessage master FormMessage, RenderMessage master msg) => [(msg, a)] -> Field sub master a selectFieldList :: (Eq a, RenderMessage master FormMessage, RenderMessage master msg) => [(msg, a)] -> Field sub master a
@ -347,10 +360,10 @@ multiSelectField :: (Eq a, RenderMessage master FormMessage)
=> GHandler sub master (OptionList a) => GHandler sub master (OptionList a)
-> Field sub master [a] -> Field sub master [a]
multiSelectField ioptlist = multiSelectField ioptlist =
Field parse view Field parse view UrlEncoded
where where
parse [] = return $ Right Nothing parse [] _ = return $ Right Nothing
parse optlist = do parse optlist _ = do
mapopt <- olReadExternal <$> ioptlist mapopt <- olReadExternal <$> ioptlist
case mapM mapopt optlist of case mapM mapopt optlist of
Nothing -> return $ Left "Error parsing values" Nothing -> return $ Left "Error parsing values"
@ -395,7 +408,7 @@ $newline never
boolField :: RenderMessage master FormMessage => Field sub master Bool boolField :: RenderMessage master FormMessage => Field sub master Bool
boolField = Field boolField = Field
{ fieldParse = return . boolParser { fieldParse = \e _ -> return $ boolParser e
, fieldView = \theId name attrs val isReq -> [whamlet| , fieldView = \theId name attrs val isReq -> [whamlet|
$newline never $newline never
$if not isReq $if not isReq
@ -409,6 +422,7 @@ $newline never
<input id=#{theId}-no *{attrs} type=radio name=#{name} value=no :showVal not val:checked> <input id=#{theId}-no *{attrs} type=radio name=#{name} value=no :showVal not val:checked>
<label for=#{theId}-no>_{MsgBoolNo} <label for=#{theId}-no>_{MsgBoolNo}
|] |]
, fieldEnctype = UrlEncoded
} }
where where
boolParser [] = Right Nothing boolParser [] = Right Nothing
@ -430,11 +444,12 @@ $newline never
-- --
checkBoxField :: RenderMessage m FormMessage => Field s m Bool checkBoxField :: RenderMessage m FormMessage => Field s m Bool
checkBoxField = Field checkBoxField = Field
{ fieldParse = return . checkBoxParser { fieldParse = \e _ -> return $ checkBoxParser e
, fieldView = \theId name attrs val _ -> [whamlet| , fieldView = \theId name attrs val _ -> [whamlet|
$newline never $newline never
<input id=#{theId} *{attrs} type=checkbox name=#{name} value=yes :showVal id val:checked> <input id=#{theId} *{attrs} type=checkbox name=#{name} value=yes :showVal id val:checked>
|] |]
, fieldEnctype = UrlEncoded
} }
where where
@ -499,7 +514,7 @@ selectFieldHelper
-> (Text -> Text -> [(Text, Text)] -> Text -> Bool -> Text -> GWidget sub master ()) -> (Text -> Text -> [(Text, Text)] -> Text -> Bool -> Text -> GWidget sub master ())
-> GHandler sub master (OptionList a) -> Field sub master a -> GHandler sub master (OptionList a) -> Field sub master a
selectFieldHelper outside onOpt inside opts' = Field selectFieldHelper outside onOpt inside opts' = Field
{ fieldParse = \x -> do { fieldParse = \x _ -> do
opts <- opts' opts <- opts'
return $ selectParser opts x return $ selectParser opts x
, fieldView = \theId name attrs val isReq -> do , fieldView = \theId name attrs val isReq -> do
@ -513,6 +528,7 @@ selectFieldHelper outside onOpt inside opts' = Field
(optionExternalValue opt) (optionExternalValue opt)
((render opts val) == optionExternalValue opt) ((render opts val) == optionExternalValue opt)
(optionDisplay opt) (optionDisplay opt)
, fieldEnctype = UrlEncoded
} }
where where
render _ (Left _) = "" render _ (Left _) = ""
@ -525,6 +541,18 @@ selectFieldHelper outside onOpt inside opts' = Field
Nothing -> Left $ SomeMessage $ MsgInvalidEntry x Nothing -> Left $ SomeMessage $ MsgInvalidEntry x
Just y -> Right $ Just y Just y -> Right $ Just y
fileField :: RenderMessage master FormMessage => Field sub master FileInfo
fileField = Field
{ fieldParse = \_ files -> return $
case files of
[] -> Right Nothing
file:_ -> Right $ Just file
, fieldView = \id' name attrs _ isReq -> toWidget [hamlet|
<input id=#{id'} name=#{name} *{attrs} type=file :isReq:required>
|]
, fieldEnctype = Multipart
}
fileAFormReq :: RenderMessage master FormMessage => FieldSettings master -> AForm sub master FileInfo fileAFormReq :: RenderMessage master FormMessage => FieldSettings master -> AForm sub master FileInfo
fileAFormReq fs = AForm $ \(master, langs) menvs ints -> do fileAFormReq fs = AForm $ \(master, langs) menvs ints -> do
let (name, ints') = let (name, ints') =

View File

@ -65,6 +65,7 @@ import qualified Data.Map as Map
import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding as TE
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Control.Arrow (first) import Control.Arrow (first)
import Yesod.Request (FileInfo)
-- | Get a unique identifier. -- | Get a unique identifier.
newFormIdent :: MForm sub master Text newFormIdent :: MForm sub master Text
@ -119,6 +120,7 @@ mhelper :: Field sub master a
-> MForm sub master (FormResult b, FieldView sub master) -> MForm sub master (FormResult b, FieldView sub master)
mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do
tell fieldEnctype
mp <- askParams mp <- askParams
name <- maybe newFormIdent return fsName name <- maybe newFormIdent return fsName
theId <- lift $ maybe newIdent return fsId theId <- lift $ maybe newIdent return fsId
@ -128,8 +130,10 @@ mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do
case mp of case mp of
Nothing -> return (FormMissing, maybe (Left "") Right mdef) Nothing -> return (FormMissing, maybe (Left "") Right mdef)
Just p -> do Just p -> do
mfs <- askFiles
let mvals = fromMaybe [] $ Map.lookup name p let mvals = fromMaybe [] $ Map.lookup name p
emx <- lift $ fieldParse mvals files = fromMaybe [] $ mfs >>= Map.lookup name
emx <- lift $ fieldParse mvals files
return $ case emx of return $ case emx of
Left (SomeMessage e) -> (FormFailure [renderMessage master langs e], maybe (Left "") Left (listToMaybe mvals)) Left (SomeMessage e) -> (FormFailure [renderMessage master langs e], maybe (Left "") Left (listToMaybe mvals))
Right mx -> Right mx ->
@ -371,8 +375,8 @@ checkMMap :: RenderMessage master msg
-> Field sub master a -> Field sub master a
-> Field sub master b -> Field sub master b
checkMMap f inv field = field checkMMap f inv field = field
{ fieldParse = \ts -> do { fieldParse = \ts fs -> do
e1 <- fieldParse field ts e1 <- fieldParse field ts fs
case e1 of case e1 of
Left msg -> return $ Left msg Left msg -> return $ Left msg
Right Nothing -> return $ Right Nothing Right Nothing -> return $ Right Nothing
@ -393,8 +397,8 @@ checkMMod = checkMMap
-- | Allows you to overwrite the error message on parse error. -- | Allows you to overwrite the error message on parse error.
customErrorMessage :: SomeMessage master -> Field sub master a -> Field sub master a customErrorMessage :: SomeMessage master -> Field sub master a -> Field sub master a
customErrorMessage msg field = field { fieldParse = \ts -> fmap (either customErrorMessage msg field = field { fieldParse = \ts fs -> fmap (either
(const $ Left msg) Right) $ fieldParse field ts } (const $ Left msg) Right) $ fieldParse field ts fs }
-- | Generate a 'FieldSettings' from the given label. -- | Generate a 'FieldSettings' from the given label.
fieldSettingsLabel :: RenderMessage master msg => msg -> FieldSettings master fieldSettingsLabel :: RenderMessage master msg => msg -> FieldSettings master
@ -414,7 +418,7 @@ aformM action = AForm $ \_ _ ints -> do
-- Since 1.1 -- Since 1.1
parseHelper :: (Monad m, RenderMessage master FormMessage) parseHelper :: (Monad m, RenderMessage master FormMessage)
=> (Text -> Either FormMessage a) => (Text -> Either FormMessage a)
-> [Text] -> m (Either (SomeMessage master) (Maybe a)) -> [Text] -> [FileInfo] -> m (Either (SomeMessage master) (Maybe a))
parseHelper _ [] = return $ Right Nothing parseHelper _ [] _ = return $ Right Nothing
parseHelper _ ("":_) = return $ Right Nothing parseHelper _ ("":_) _ = return $ Right Nothing
parseHelper f (x:_) = return $ either (Left . SomeMessage) (Right . Just) $ f x parseHelper f (x:_) _ = return $ either (Left . SomeMessage) (Right . Just) $ f x

View File

@ -17,16 +17,17 @@ import Control.Monad (liftM)
import Yesod.Message (RenderMessage (..), SomeMessage (..)) import Yesod.Message (RenderMessage (..), SomeMessage (..))
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Control.Arrow ((***))
type DText = [Text] -> [Text] type DText = [Text] -> [Text]
newtype FormInput sub master a = FormInput { unFormInput :: master -> [Text] -> Env -> GHandler sub master (Either DText a) } newtype FormInput sub master a = FormInput { unFormInput :: master -> [Text] -> Env -> FileEnv -> GHandler sub master (Either DText a) }
instance Functor (FormInput sub master) where instance Functor (FormInput sub master) where
fmap a (FormInput f) = FormInput $ \c d e -> fmap (either Left (Right . a)) $ f c d e fmap a (FormInput f) = FormInput $ \c d e e' -> fmap (either Left (Right . a)) $ f c d e e'
instance Applicative (FormInput sub master) where instance Applicative (FormInput sub master) where
pure = FormInput . const . const . const . return . Right pure = FormInput . const . const . const . const . return . Right
(FormInput f) <*> (FormInput x) = FormInput $ \c d e -> do (FormInput f) <*> (FormInput x) = FormInput $ \c d e e' -> do
res1 <- f c d e res1 <- f c d e e'
res2 <- x c d e res2 <- x c d e e'
return $ case (res1, res2) of return $ case (res1, res2) of
(Left a, Left b) -> Left $ a . b (Left a, Left b) -> Left $ a . b
(Left a, _) -> Left a (Left a, _) -> Left a
@ -34,18 +35,20 @@ instance Applicative (FormInput sub master) where
(Right a, Right b) -> Right $ a b (Right a, Right b) -> Right $ a b
ireq :: (RenderMessage master FormMessage) => Field sub master a -> Text -> FormInput sub master a ireq :: (RenderMessage master FormMessage) => Field sub master a -> Text -> FormInput sub master a
ireq field name = FormInput $ \m l env -> do ireq field name = FormInput $ \m l env fenv -> do
let filteredEnv = fromMaybe [] $ Map.lookup name env let filteredEnv = fromMaybe [] $ Map.lookup name env
emx <- fieldParse field $ filteredEnv filteredFEnv = fromMaybe [] $ Map.lookup name fenv
emx <- fieldParse field filteredEnv filteredFEnv
return $ case emx of return $ case emx of
Left (SomeMessage e) -> Left $ (:) $ renderMessage m l e Left (SomeMessage e) -> Left $ (:) $ renderMessage m l e
Right Nothing -> Left $ (:) $ renderMessage m l $ MsgInputNotFound name Right Nothing -> Left $ (:) $ renderMessage m l $ MsgInputNotFound name
Right (Just a) -> Right a Right (Just a) -> Right a
iopt :: Field sub master a -> Text -> FormInput sub master (Maybe a) iopt :: Field sub master a -> Text -> FormInput sub master (Maybe a)
iopt field name = FormInput $ \m l env -> do iopt field name = FormInput $ \m l env fenv -> do
let filteredEnv = fromMaybe [] $ Map.lookup name env let filteredEnv = fromMaybe [] $ Map.lookup name env
emx <- fieldParse field $ filteredEnv filteredFEnv = fromMaybe [] $ Map.lookup name fenv
emx <- fieldParse field filteredEnv filteredFEnv
return $ case emx of return $ case emx of
Left (SomeMessage e) -> Left $ (:) $ renderMessage m l e Left (SomeMessage e) -> Left $ (:) $ renderMessage m l e
Right x -> Right x Right x -> Right x
@ -55,7 +58,7 @@ runInputGet (FormInput f) = do
env <- liftM (toMap . reqGetParams) getRequest env <- liftM (toMap . reqGetParams) getRequest
m <- getYesod m <- getYesod
l <- languages l <- languages
emx <- f m l env emx <- f m l env Map.empty
case emx of case emx of
Left errs -> invalidArgs $ errs [] Left errs -> invalidArgs $ errs []
Right x -> return x Right x -> return x
@ -65,10 +68,10 @@ toMap = Map.unionsWith (++) . map (\(x, y) -> Map.singleton x [y])
runInputPost :: FormInput sub master a -> GHandler sub master a runInputPost :: FormInput sub master a -> GHandler sub master a
runInputPost (FormInput f) = do runInputPost (FormInput f) = do
env <- liftM (toMap . fst) runRequestBody (env, fenv) <- liftM (toMap *** toMap) runRequestBody
m <- getYesod m <- getYesod
l <- languages l <- languages
emx <- f m l env emx <- f m l env fenv
case emx of case emx of
Left errs -> invalidArgs $ errs [] Left errs -> invalidArgs $ errs []
Right x -> return x Right x -> return x

View File

@ -85,6 +85,7 @@ $(function(){
} }
}); });
|] |]
, fieldEnctype = UrlEncoded
} }
where where
showVal = either id (pack . show) showVal = either id (pack . show)
@ -114,6 +115,7 @@ $newline never
toWidget [julius| toWidget [julius|
$(function(){$("##{theId}").autocomplete({source:"@{src}",minLength:2})}); $(function(){$("##{theId}").autocomplete({source:"@{src}",minLength:2})});
|] |]
, fieldEnctype = UrlEncoded
} }
addScript' :: (master -> Either (Route master) Text) -> GWidget sub master () addScript' :: (master -> Either (Route master) Text) -> GWidget sub master ()

View File

@ -35,7 +35,7 @@ class Yesod a => YesodNic a where
nicHtmlField :: YesodNic master => Field sub master Html nicHtmlField :: YesodNic master => Field sub master Html
nicHtmlField = Field nicHtmlField = Field
{ fieldParse = return . Right . fmap (preEscapedText . sanitizeBalance) . listToMaybe { fieldParse = \e _ -> return . Right . fmap (preEscapedText . sanitizeBalance) . listToMaybe $ e
, fieldView = \theId name attrs val _isReq -> do , fieldView = \theId name attrs val _isReq -> do
toWidget [shamlet| toWidget [shamlet|
$newline never $newline never
@ -51,6 +51,7 @@ bkLib.onDomLoaded(function(){new nicEditor({fullPanel:true}).panelInstance("#{th
_ -> [julius| _ -> [julius|
(function(){new nicEditor({fullPanel:true}).panelInstance("#{theId}")})(); (function(){new nicEditor({fullPanel:true}).panelInstance("#{theId}")})();
|] |]
, fieldEnctype = UrlEncoded
} }
where where
showVal = either id (pack . renderHtml) showVal = either id (pack . renderHtml)

View File

@ -124,13 +124,14 @@ data FieldView sub master = FieldView
} }
data Field sub master a = Field data Field sub master a = Field
{ fieldParse :: [Text] -> GHandler sub master (Either (SomeMessage master) (Maybe a)) { fieldParse :: [Text] -> [FileInfo] -> GHandler sub master (Either (SomeMessage master) (Maybe a))
, fieldView :: Text -- ^ ID , fieldView :: Text -- ^ ID
-> Text -- ^ Name -> Text -- ^ Name
-> [(Text, Text)] -- ^ Attributes -> [(Text, Text)] -- ^ Attributes
-> Either Text a -- ^ Either (invalid text) or (legitimate result) -> Either Text a -- ^ Either (invalid text) or (legitimate result)
-> Bool -- ^ Required? -> Bool -- ^ Required?
-> GWidget sub master () -> GWidget sub master ()
, fieldEnctype :: Enctype
} }
data FormMessage = MsgInvalidInteger Text data FormMessage = MsgInvalidInteger Text

View File

@ -111,8 +111,13 @@ getValidR = do
main = toWaiApp HelloForms >>= run 3000 main = toWaiApp HelloForms >>= run 3000
fileForm = renderTable $ pure (,) fileForm = renderTable $ pure (,)
<*> fileAFormReq "Required file" <*> (FileInfo' <$> areq fileField "Required file" Nothing)
<*> fileAFormOpt "Optional file" <*> (fmap FileInfo' <$> aopt fileField "Optional file" Nothing)
newtype FileInfo' = FileInfo' FileInfo
instance Show FileInfo' where
show (FileInfo' f) = show (fileName f, fileContentType f)
getFileR = do getFileR = do
((res, form), enctype) <- runFormPost fileForm ((res, form), enctype) <- runFormPost fileForm