MassInput.hs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. {-# LANGUAGE QuasiQuotes #-}
  2. {-# LANGUAGE OverloadedStrings #-}
  3. {-# LANGUAGE TypeFamilies #-}
  4. {-# LANGUAGE FlexibleContexts #-}
  5. {-# LANGUAGE CPP#-}
  6. -- | A module providing a means of creating multiple input forms, such as a
  7. -- list of 0 or more recipients.
  8. module Yesod.Form.MassInput
  9. ( inputList
  10. , massDivs
  11. , massTable
  12. ) where
  13. import Yesod.Form.Types
  14. import Yesod.Form.Functions
  15. import Yesod.Form.Fields (checkBoxField)
  16. import Yesod.Core
  17. import Control.Monad.Trans.RWS (get, put, ask)
  18. import Data.Maybe (fromMaybe)
  19. import Data.Text.Read (decimal)
  20. import Control.Monad (liftM)
  21. import Data.Either (partitionEithers)
  22. import Data.Traversable (sequenceA)
  23. import qualified Data.Map as Map
  24. import Data.Maybe (listToMaybe)
  25. down :: Monad m => Int -> MForm m ()
  26. down 0 = return ()
  27. down i | i < 0 = error "called down with a negative number"
  28. down i = do
  29. is <- get
  30. put $ IntCons 0 is
  31. down $ i - 1
  32. up :: Monad m => Int -> MForm m ()
  33. up 0 = return ()
  34. up i | i < 0 = error "called down with a negative number"
  35. up i = do
  36. is <- get
  37. case is of
  38. IntSingle _ -> error "up on IntSingle"
  39. IntCons _ is' -> put is' >> newFormIdent >> return ()
  40. up $ i - 1
  41. -- | Generate a form that accepts 0 or more values from the user, allowing the
  42. -- user to specify that a new row is necessary.
  43. inputList :: (m ~ HandlerT site IO, xml ~ WidgetT site IO (), RenderMessage site FormMessage)
  44. => Html
  45. -- ^ label for the form
  46. -> ([[FieldView site]] -> xml)
  47. -- ^ how to display the rows, usually either 'massDivs' or 'massTable'
  48. -> (Maybe a -> AForm (HandlerT site IO) a)
  49. -- ^ display a single row of the form, where @Maybe a@ gives the
  50. -- previously submitted value
  51. -> Maybe [a]
  52. -- ^ default initial values for the form
  53. -> AForm (HandlerT site IO) [a]
  54. inputList label fixXml single mdef = formToAForm $ do
  55. theId <- lift newIdent
  56. down 1
  57. countName <- newFormIdent
  58. addName <- newFormIdent
  59. (menv, _, _) <- ask
  60. let readInt t =
  61. case decimal t of
  62. Right (i, "") -> Just i
  63. _ -> Nothing
  64. let vals =
  65. case menv of
  66. Nothing -> map Just $ fromMaybe [] mdef
  67. Just (env, _) ->
  68. let toAdd = maybe False (const True) $ Map.lookup addName env
  69. count' = fromMaybe 0 $ Map.lookup countName env >>= listToMaybe >>= readInt
  70. count = (if toAdd then 1 else 0) + count'
  71. in replicate count Nothing
  72. let count = length vals
  73. (res, xmls, views) <- liftM fixme $ mapM (withDelete . single) vals
  74. up 1
  75. return (res, [FieldView
  76. { fvLabel = label
  77. , fvTooltip = Nothing
  78. , fvId = theId
  79. , fvInput = [whamlet|
  80. $newline never
  81. ^{fixXml views}
  82. <p>
  83. $forall xml <- xmls
  84. ^{xml}
  85. <input .count type=hidden name=#{countName} value=#{count}>
  86. <input type=checkbox name=#{addName}>
  87. Add another row
  88. |]
  89. , fvErrors = Nothing
  90. , fvRequired = False
  91. }])
  92. withDelete :: (xml ~ WidgetT site IO (), RenderMessage site FormMessage)
  93. => AForm (HandlerT site IO) a
  94. -> MForm (HandlerT site IO) (Either xml (FormResult a, [FieldView site]))
  95. withDelete af = do
  96. down 1
  97. deleteName <- newFormIdent
  98. (menv, _, _) <- ask
  99. res <- case menv >>= Map.lookup deleteName . fst of
  100. Just ("yes":_) -> return $ Left [whamlet|
  101. $newline never
  102. <input type=hidden name=#{deleteName} value=yes>
  103. |]
  104. _ -> do
  105. (_, xml2) <- aFormToForm $ areq checkBoxField FieldSettings
  106. { fsLabel = SomeMessage MsgDelete
  107. , fsTooltip = Nothing
  108. , fsName = Just deleteName
  109. , fsId = Nothing
  110. , fsAttrs = []
  111. } $ Just False
  112. (res, xml) <- aFormToForm af
  113. return $ Right (res, xml $ xml2 [])
  114. up 1
  115. return res
  116. fixme :: (xml ~ WidgetT site IO ())
  117. => [Either xml (FormResult a, [FieldView site])]
  118. -> (FormResult [a], [xml], [[FieldView site]])
  119. fixme eithers =
  120. (res, xmls, map snd rest)
  121. where
  122. (xmls, rest) = partitionEithers eithers
  123. res = sequenceA $ map fst rest
  124. massDivs, massTable
  125. :: [[FieldView site]]
  126. -> WidgetT site IO ()
  127. massDivs viewss = [whamlet|
  128. $newline never
  129. $forall views <- viewss
  130. <fieldset>
  131. $forall view <- views
  132. <div :fvRequired view:.required :not $ fvRequired view:.optional>
  133. <label for=#{fvId view}>#{fvLabel view}
  134. $maybe tt <- fvTooltip view
  135. <div .tooltip>#{tt}
  136. ^{fvInput view}
  137. $maybe err <- fvErrors view
  138. <div .errors>#{err}
  139. |]
  140. massTable viewss = [whamlet|
  141. $newline never
  142. $forall views <- viewss
  143. <fieldset>
  144. <table>
  145. $forall view <- views
  146. <tr :fvRequired view:.required :not $ fvRequired view:.optional>
  147. <td>
  148. <label for=#{fvId view}>#{fvLabel view}
  149. $maybe tt <- fvTooltip view
  150. <div .tooltip>#{tt}
  151. <td>^{fvInput view}
  152. $maybe err <- fvErrors view
  153. <td .errors>#{err}
  154. |]