Better multiSelectField (#183)
This commit is contained in:
parent
0e076d5198
commit
4d5c123935
@ -86,6 +86,7 @@ import qualified Text.Hamlet as NP
|
|||||||
import Data.Text.Lazy.Builder (fromLazyText)
|
import Data.Text.Lazy.Builder (fromLazyText)
|
||||||
import Text.Blaze (toHtml, preEscapedLazyText)
|
import Text.Blaze (toHtml, preEscapedLazyText)
|
||||||
import Control.Monad.Base (MonadBase (liftBase))
|
import Control.Monad.Base (MonadBase (liftBase))
|
||||||
|
import Control.Arrow (first)
|
||||||
|
|
||||||
-- | A generic widget, allowing specification of both the subsite and master
|
-- | A generic widget, allowing specification of both the subsite and master
|
||||||
-- site datatypes. While this is simply a @WriterT@, we define a newtype for
|
-- site datatypes. While this is simply a @WriterT@, we define a newtype for
|
||||||
@ -320,7 +321,7 @@ liftW = lift
|
|||||||
|
|
||||||
-- Instances for GWidget
|
-- Instances for GWidget
|
||||||
instance Functor (GWidget sub master) where
|
instance Functor (GWidget sub master) where
|
||||||
fmap f (GWidget x) = GWidget (fmap (\(a, w) -> (f a, w)) x)
|
fmap f (GWidget x) = GWidget (fmap (first f) x)
|
||||||
instance Applicative (GWidget sub master) where
|
instance Applicative (GWidget sub master) where
|
||||||
pure a = GWidget $ pure (a, mempty)
|
pure a = GWidget $ pure (a, mempty)
|
||||||
GWidget f <*> GWidget v =
|
GWidget f <*> GWidget v =
|
||||||
|
|||||||
@ -18,22 +18,23 @@ module Yesod.Form.Fields
|
|||||||
, htmlField
|
, htmlField
|
||||||
, emailField
|
, emailField
|
||||||
, searchField
|
, searchField
|
||||||
, selectField
|
|
||||||
, multiSelectField
|
|
||||||
, AutoFocus
|
, AutoFocus
|
||||||
, urlField
|
, urlField
|
||||||
, doubleField
|
, doubleField
|
||||||
, parseDate
|
, parseDate
|
||||||
, parseTime
|
, parseTime
|
||||||
, Textarea (..)
|
, Textarea (..)
|
||||||
, radioField
|
|
||||||
, boolField
|
, boolField
|
||||||
-- * File 'AForm's
|
-- * File 'AForm's
|
||||||
, fileAFormReq
|
, fileAFormReq
|
||||||
, fileAFormOpt
|
, fileAFormOpt
|
||||||
-- * Options
|
-- * Options
|
||||||
, selectField'
|
, selectField
|
||||||
, radioField'
|
, selectFieldList
|
||||||
|
, radioField
|
||||||
|
, radioFieldList
|
||||||
|
, multiSelectField
|
||||||
|
, multiSelectFieldList
|
||||||
, Option (..)
|
, Option (..)
|
||||||
, OptionList (..)
|
, OptionList (..)
|
||||||
, mkOptionList
|
, mkOptionList
|
||||||
@ -93,6 +94,7 @@ import Control.Arrow ((&&&))
|
|||||||
#define HTML $html
|
#define HTML $html
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
import Control.Applicative ((<$>))
|
||||||
|
|
||||||
defaultFormMessage :: FormMessage -> Text
|
defaultFormMessage :: FormMessage -> Text
|
||||||
defaultFormMessage = englishFormMessage
|
defaultFormMessage = englishFormMessage
|
||||||
@ -303,25 +305,48 @@ urlField = Field
|
|||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
|
|
||||||
selectField :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
|
selectFieldList :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
|
||||||
selectField = selectField' . optionsPairs
|
selectFieldList = selectField . optionsPairs
|
||||||
|
|
||||||
selectField' :: (Eq a, RenderMessage master FormMessage) => GHandler sub master (OptionList a) -> Field sub master a
|
selectField :: (Eq a, RenderMessage master FormMessage) => GHandler sub master (OptionList a) -> Field sub master a
|
||||||
selectField' = selectFieldHelper
|
selectField = selectFieldHelper
|
||||||
(\theId name inside -> [WHAMLET|<select ##{theId} name=#{name}>^{inside}|]) -- outside
|
(\theId name inside -> [WHAMLET|<select ##{theId} name=#{name}>^{inside}|]) -- outside
|
||||||
(\_theId _name isSel -> [WHAMLET|<option value=none :isSel:selected>_{MsgSelectNone}|]) -- onOpt
|
(\_theId _name isSel -> [WHAMLET|<option value=none :isSel:selected>_{MsgSelectNone}|]) -- onOpt
|
||||||
(\_theId _name theClass value isSel text -> [WHAMLET|<option value=#{value} :isSel:selected :not (null theClass):class="#{T.intercalate " " theClass}">#{text}|]) -- inside
|
(\_theId _name theClass value isSel text -> [WHAMLET|<option value=#{value} :isSel:selected :not (null theClass):class="#{T.intercalate " " theClass}">#{text}|]) -- inside
|
||||||
|
|
||||||
multiSelectField :: (Show a, Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master [a]
|
multiSelectFieldList :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master [a]
|
||||||
multiSelectField = multiSelectFieldHelper
|
multiSelectFieldList = multiSelectField . optionsPairs
|
||||||
(\theId name inside -> [WHAMLET|<select ##{theId} multiple name=#{name}>^{inside}|])
|
|
||||||
(\_theId _name theClass value isSel text -> [WHAMLET|<option value=#{value} :isSel:selected :not (null theClass):class="#{T.intercalate " " theClass}">#{text}|])
|
|
||||||
|
|
||||||
radioField :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
|
multiSelectField :: (Eq a, RenderMessage master FormMessage)
|
||||||
radioField = radioField' . optionsPairs
|
=> GHandler sub master (OptionList a)
|
||||||
|
-> Field sub master [a]
|
||||||
|
multiSelectField ioptlist =
|
||||||
|
Field parse view
|
||||||
|
where
|
||||||
|
parse [] = return $ Right Nothing
|
||||||
|
parse optlist = do
|
||||||
|
mapopt <- olReadExternal <$> ioptlist
|
||||||
|
case mapM mapopt optlist of
|
||||||
|
Nothing -> return $ Left "Error parsing values"
|
||||||
|
Just res -> return $ Right $ Just res
|
||||||
|
|
||||||
radioField' :: (Eq a, RenderMessage master FormMessage) => GHandler sub master (OptionList a) -> Field sub master a
|
view theId name theClass val isReq = do
|
||||||
radioField' = selectFieldHelper
|
opts <- fmap olOptions $ lift ioptlist
|
||||||
|
let selOpts = map (id &&& (optselected val)) opts
|
||||||
|
[whamlet|
|
||||||
|
<select ##{theId} name=#{name} :isReq:required multiple :not (null theClass):class=#{T.intercalate " " theClass}>
|
||||||
|
$forall (opt, optsel) <- selOpts
|
||||||
|
<option value=#{optionExternalValue opt} :optsel:selected>#{optionDisplay opt}
|
||||||
|
|]
|
||||||
|
where
|
||||||
|
optselected (Left _) _ = False
|
||||||
|
optselected (Right vals) opt = (optionInternalValue opt) `elem` vals
|
||||||
|
|
||||||
|
radioFieldList :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
|
||||||
|
radioFieldList = radioField . optionsPairs
|
||||||
|
|
||||||
|
radioField :: (Eq a, RenderMessage master FormMessage) => GHandler sub master (OptionList a) -> Field sub master a
|
||||||
|
radioField = selectFieldHelper
|
||||||
(\theId _name inside -> [WHAMLET|<div ##{theId}>^{inside}|])
|
(\theId _name inside -> [WHAMLET|<div ##{theId}>^{inside}|])
|
||||||
(\theId name isSel -> [WHAMLET|
|
(\theId name isSel -> [WHAMLET|
|
||||||
<div>
|
<div>
|
||||||
@ -360,30 +385,6 @@ boolField = Field
|
|||||||
t -> Left $ SomeMessage $ MsgInvalidBool t
|
t -> Left $ SomeMessage $ MsgInvalidBool t
|
||||||
showVal = either (\_ -> False)
|
showVal = either (\_ -> False)
|
||||||
|
|
||||||
multiSelectFieldHelper :: (Show a, Eq a)
|
|
||||||
=> (Text -> Text -> GWidget sub master () -> GWidget sub master ())
|
|
||||||
-> (Text -> Text -> [Text] -> Text -> Bool -> Text -> GWidget sub master ())
|
|
||||||
-> [(Text, a)] -> Field sub master [a]
|
|
||||||
multiSelectFieldHelper outside inside opts = Field
|
|
||||||
{ fieldParse = return . selectParser
|
|
||||||
, fieldView = \theId name theClass vals _ ->
|
|
||||||
outside theId name $ do
|
|
||||||
flip mapM_ pairs $ \pair -> inside
|
|
||||||
theId
|
|
||||||
name
|
|
||||||
theClass
|
|
||||||
(pack $ show $ fst pair)
|
|
||||||
((fst pair) `elem` (either (\_ -> []) selectedVals vals)) -- We are presuming that select fields can't hold invalid values
|
|
||||||
(fst $ snd pair)
|
|
||||||
}
|
|
||||||
where
|
|
||||||
pairs = zip [1 :: Int ..] opts -- FIXME use IntMap
|
|
||||||
rpairs = zip (map snd opts) [1 :: Int ..]
|
|
||||||
selectedVals vals = map snd $ filter (\y -> fst y `elem` vals) rpairs
|
|
||||||
selectParser [] = Right Nothing
|
|
||||||
selectParser xs | not $ null (["", "none"] `intersect` xs) = Right Nothing
|
|
||||||
| otherwise = (Right . Just . map snd . catMaybes . map (\y -> lookup y pairs) . nub . map fst . rights . map Data.Text.Read.decimal) xs
|
|
||||||
|
|
||||||
data OptionList a = OptionList
|
data OptionList a = OptionList
|
||||||
{ olOptions :: [Option a]
|
{ olOptions :: [Option a]
|
||||||
, olReadExternal :: Text -> Maybe a
|
, olReadExternal :: Text -> Maybe a
|
||||||
|
|||||||
@ -27,12 +27,12 @@ myForm = fixType $ runFormGet $ renderDivs $ pure (,,,,,,,,)
|
|||||||
<*> areq boolField "Bool field" Nothing
|
<*> areq boolField "Bool field" Nothing
|
||||||
<*> aopt boolField "Opt bool field" Nothing
|
<*> aopt boolField "Opt bool field" Nothing
|
||||||
<*> areq textField "Text field" Nothing
|
<*> areq textField "Text field" Nothing
|
||||||
<*> areq (selectField fruits) "Select field" Nothing
|
<*> areq (selectFieldList fruits) "Select field" Nothing
|
||||||
<*> aopt (selectField fruits) "Opt select field" Nothing
|
<*> aopt (selectFieldList fruits) "Opt select field" Nothing
|
||||||
<*> areq (multiSelectField fruits) "Multi select field" Nothing
|
<*> areq (multiSelectFieldList fruits) "Multi select field" Nothing
|
||||||
<*> aopt (multiSelectField fruits) "Opt multi select field" Nothing
|
<*> aopt (multiSelectFieldList fruits) "Opt multi select field" Nothing
|
||||||
<*> aopt intField "Opt int field" Nothing
|
<*> aopt intField "Opt int field" Nothing
|
||||||
<*> aopt (radioField fruits) "Opt radio" Nothing
|
<*> aopt (radioFieldList fruits) "Opt radio" Nothing
|
||||||
|
|
||||||
data HelloForms = HelloForms
|
data HelloForms = HelloForms
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user