PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Reflex/Dom/Widget/Input.hs

https://gitlab.com/pavelkogan/reflex-dom
Haskell | 322 lines | 234 code | 43 blank | 45 comment | 7 complexity | 3c51e0cd83f5def498d71bc4fac82bfe MD5 | raw file
  1. {-# LANGUAGE ConstraintKinds, TypeFamilies, FlexibleContexts, DataKinds, GADTs, ScopedTypeVariables, FlexibleInstances, RecursiveDo, TemplateHaskell #-}
  2. module Reflex.Dom.Widget.Input (module Reflex.Dom.Widget.Input, def, (&), (.~)) where
  3. import Prelude
  4. import Reflex.Dom.Class
  5. import Reflex.Dom.Widget.Basic
  6. import Reflex
  7. import Reflex.Host.Class
  8. import GHCJS.DOM.HTMLInputElement
  9. import GHCJS.DOM.HTMLTextAreaElement
  10. import GHCJS.DOM.Element
  11. import GHCJS.DOM.HTMLSelectElement
  12. import GHCJS.DOM.EventM
  13. import GHCJS.DOM.File
  14. import GHCJS.DOM.FileList
  15. import Data.Monoid
  16. import Data.Map as Map
  17. import Control.Lens
  18. import Control.Monad hiding (forM_)
  19. import Control.Monad.IO.Class
  20. import Data.Default
  21. import Data.Maybe
  22. import Safe
  23. import Data.Dependent.Sum (DSum (..))
  24. data TextInput t
  25. = TextInput { _textInput_value :: Dynamic t String
  26. , _textInput_input :: Event t String
  27. , _textInput_keypress :: Event t Int
  28. , _textInput_keydown :: Event t Int
  29. , _textInput_keyup :: Event t Int
  30. , _textInput_hasFocus :: Dynamic t Bool
  31. , _textInput_element :: HTMLInputElement
  32. }
  33. data TextInputConfig t
  34. = TextInputConfig { _textInputConfig_inputType :: String
  35. , _textInputConfig_initialValue :: String
  36. , _textInputConfig_setValue :: Event t String
  37. , _textInputConfig_attributes :: Dynamic t (Map String String)
  38. }
  39. instance Reflex t => Default (TextInputConfig t) where
  40. def = TextInputConfig { _textInputConfig_inputType = "text"
  41. , _textInputConfig_initialValue = ""
  42. , _textInputConfig_setValue = never
  43. , _textInputConfig_attributes = constDyn mempty
  44. }
  45. textInput :: MonadWidget t m => TextInputConfig t -> m (TextInput t)
  46. textInput (TextInputConfig inputType initial eSetValue dAttrs) = do
  47. e <- liftM castToHTMLInputElement $ buildEmptyElement "input" =<< mapDyn (Map.insert "type" inputType) dAttrs
  48. liftIO $ htmlInputElementSetValue e initial
  49. performEvent_ $ fmap (liftIO . htmlInputElementSetValue e) eSetValue
  50. eChange <- wrapDomEvent e elementOninput $ liftIO $ htmlInputElementGetValue e
  51. postGui <- askPostGui
  52. runWithActions <- askRunWithActions
  53. eChangeFocus <- newEventWithTrigger $ \eChangeFocusTrigger -> do
  54. unsubscribeOnblur <- liftIO $ elementOnblur e $ liftIO $ do
  55. postGui $ runWithActions [eChangeFocusTrigger :=> False]
  56. unsubscribeOnfocus <- liftIO $ elementOnfocus e $ liftIO $ do
  57. postGui $ runWithActions [eChangeFocusTrigger :=> True]
  58. return $ liftIO $ unsubscribeOnblur >> unsubscribeOnfocus
  59. dFocus <- holdDyn False eChangeFocus
  60. eKeypress <- wrapDomEvent e elementOnkeypress getKeyEvent
  61. eKeydown <- wrapDomEvent e elementOnkeydown getKeyEvent
  62. eKeyup <- wrapDomEvent e elementOnkeyup getKeyEvent
  63. dValue <- holdDyn initial $ leftmost [eSetValue, eChange]
  64. return $ TextInput dValue eChange eKeypress eKeydown eKeyup dFocus e
  65. textInputGetEnter :: Reflex t => TextInput t -> Event t ()
  66. textInputGetEnter i = fmapMaybe (\n -> if n == keycodeEnter then Just () else Nothing) $ _textInput_keypress i
  67. data TextAreaConfig t
  68. = TextAreaConfig { _textAreaConfig_initialValue :: String
  69. , _textAreaConfig_setValue :: Event t String
  70. , _textAreaConfig_attributes :: Dynamic t (Map String String)
  71. }
  72. instance Reflex t => Default (TextAreaConfig t) where
  73. def = TextAreaConfig { _textAreaConfig_initialValue = ""
  74. , _textAreaConfig_setValue = never
  75. , _textAreaConfig_attributes = constDyn mempty
  76. }
  77. data TextArea t
  78. = TextArea { _textArea_value :: Dynamic t String
  79. , _textArea_input :: Event t String
  80. , _textArea_element :: HTMLTextAreaElement
  81. , _textArea_hasFocus :: Dynamic t Bool
  82. , _textArea_keypress :: Event t Int
  83. }
  84. textArea :: MonadWidget t m => TextAreaConfig t -> m (TextArea t)
  85. textArea (TextAreaConfig initial eSet attrs) = do
  86. e <- liftM castToHTMLTextAreaElement $ buildEmptyElement "textarea" attrs
  87. liftIO $ htmlTextAreaElementSetValue e initial
  88. postGui <- askPostGui
  89. runWithActions <- askRunWithActions
  90. eChangeFocus <- newEventWithTrigger $ \eChangeFocusTrigger -> do
  91. unsubscribeOnblur <- liftIO $ elementOnblur e $ liftIO $ do
  92. postGui $ runWithActions [eChangeFocusTrigger :=> False]
  93. unsubscribeOnfocus <- liftIO $ elementOnfocus e $ liftIO $ do
  94. postGui $ runWithActions [eChangeFocusTrigger :=> True]
  95. return $ liftIO $ unsubscribeOnblur >> unsubscribeOnfocus
  96. performEvent_ $ fmap (liftIO . htmlTextAreaElementSetValue e) eSet
  97. f <- holdDyn False eChangeFocus
  98. ev <- wrapDomEvent e elementOninput $ liftIO $ htmlTextAreaElementGetValue e
  99. v <- holdDyn initial $ leftmost [eSet, ev]
  100. eKeypress <- wrapDomEvent e elementOnkeypress getKeyEvent
  101. return $ TextArea v ev e f eKeypress
  102. data CheckboxConfig t
  103. = CheckboxConfig { _checkboxConfig_setValue :: Event t Bool
  104. , _checkboxConfig_attributes :: Dynamic t (Map String String)
  105. }
  106. instance Reflex t => Default (CheckboxConfig t) where
  107. def = CheckboxConfig { _checkboxConfig_setValue = never
  108. , _checkboxConfig_attributes = constDyn mempty
  109. }
  110. data Checkbox t
  111. = Checkbox { _checkbox_value :: Dynamic t Bool
  112. , _checkbox_change :: Event t Bool
  113. }
  114. --TODO: Make attributes possibly dynamic
  115. -- | Create an editable checkbox
  116. -- Note: if the "type" or "checked" attributes are provided as attributes, they will be ignored
  117. checkbox :: MonadWidget t m => Bool -> CheckboxConfig t -> m (Checkbox t)
  118. checkbox checked config = do
  119. attrs <- mapDyn (\c -> Map.insert "type" "checkbox" $ (if checked then Map.insert "checked" "checked" else Map.delete "checked") c) (_checkboxConfig_attributes config)
  120. e <- liftM castToHTMLInputElement $ buildEmptyElement "input" attrs
  121. eClick <- wrapDomEvent e elementOnclick $ liftIO $ htmlInputElementGetChecked e
  122. performEvent_ $ fmap (\v -> liftIO $ htmlInputElementSetChecked e $! v) $ _checkboxConfig_setValue config
  123. dValue <- holdDyn checked $ leftmost [_checkboxConfig_setValue config, eClick]
  124. return $ Checkbox dValue eClick
  125. checkboxView :: MonadWidget t m => Dynamic t (Map String String) -> Dynamic t Bool -> m (Event t Bool)
  126. checkboxView dAttrs dValue = do
  127. e <- liftM castToHTMLInputElement $ buildEmptyElement "input" =<< mapDyn (Map.insert "type" "checkbox") dAttrs
  128. eClicked <- wrapDomEvent e elementOnclick $ do
  129. preventDefault
  130. liftIO $ htmlInputElementGetChecked e
  131. schedulePostBuild $ do
  132. v <- sample $ current dValue
  133. when v $ liftIO $ htmlInputElementSetChecked e True
  134. performEvent_ $ fmap (\v -> liftIO $ htmlInputElementSetChecked e $! v) $ updated dValue
  135. return eClicked
  136. data FileInput t
  137. = FileInput { _fileInput_value :: Dynamic t [File]
  138. , _fileInput_element :: HTMLInputElement
  139. }
  140. data FileInputConfig t
  141. = FileInputConfig { _fileInputConfig_attributes :: Dynamic t (Map String String)
  142. }
  143. instance Reflex t => Default (FileInputConfig t) where
  144. def = FileInputConfig { _fileInputConfig_attributes = constDyn mempty
  145. }
  146. fileInput :: MonadWidget t m => FileInputConfig t -> m (FileInput t)
  147. fileInput (FileInputConfig dAttrs) = do
  148. e <- liftM castToHTMLInputElement $ buildEmptyElement "input" =<< mapDyn (Map.insert "type" "file") dAttrs
  149. eChange <- wrapDomEvent e elementOnchange $ liftIO $ do
  150. Just files <- htmlInputElementGetFiles e
  151. len <- fileListGetLength files
  152. mapM (liftM (fromMaybe (error "fileInput: fileListItem returned null")) . fileListItem files) $ init [0..len]
  153. dValue <- holdDyn [] eChange
  154. return $ FileInput dValue e
  155. data Dropdown t k
  156. = Dropdown { _dropdown_value :: Dynamic t k
  157. , _dropdown_change :: Event t k
  158. }
  159. data DropdownConfig t k
  160. = DropdownConfig { _dropdownConfig_setValue :: Event t k
  161. , _dropdownConfig_attributes :: Dynamic t (Map String String)
  162. }
  163. instance (Reflex t, Ord k, Show k, Read k) => Default (DropdownConfig t k) where
  164. def = DropdownConfig { _dropdownConfig_setValue = never
  165. , _dropdownConfig_attributes = constDyn mempty
  166. }
  167. --TODO: We should allow the user to specify an ordering instead of relying on the ordering of the Map
  168. --TODO: Get rid of Show k and Read k by indexing the possible values ourselves
  169. -- | Create a dropdown box
  170. -- The first argument gives the initial value of the dropdown; if it is not present in the map of options provided, it will be added with an empty string as its text
  171. dropdown :: forall k t m. (MonadWidget t m, Ord k, Show k, Read k) => k -> Dynamic t (Map k String) -> DropdownConfig t k -> m (Dropdown t k)
  172. dropdown k0 options (DropdownConfig setK attrs) = do
  173. (eRaw, _) <- elDynAttr' "select" attrs $ do
  174. optionsWithDefault <- mapDyn (`Map.union` (k0 =: "")) options
  175. listWithKey optionsWithDefault $ \k v -> do
  176. elAttr "option" ("value" =: show k <> if k == k0 then "selected" =: "selected" else mempty) $ dynText v
  177. let e = castToHTMLSelectElement $ _el_element eRaw
  178. performEvent_ $ fmap (liftIO . htmlSelectElementSetValue e . show) setK
  179. eChange <- wrapDomEvent e elementOnchange $ do
  180. kStr <- liftIO $ htmlSelectElementGetValue e
  181. return $ readMay kStr
  182. let readKey opts mk = fromMaybe k0 $ do
  183. k <- mk
  184. guard $ Map.member k opts
  185. return k
  186. dValue <- combineDyn readKey options =<< holdDyn (Just k0) (leftmost [eChange, fmap Just setK])
  187. return $ Dropdown dValue (attachDynWith readKey options eChange)
  188. liftM concat $ mapM makeLenses
  189. [ ''TextAreaConfig
  190. , ''TextArea
  191. , ''TextInputConfig
  192. , ''TextInput
  193. , ''DropdownConfig
  194. , ''Dropdown
  195. , ''CheckboxConfig
  196. , ''Checkbox
  197. ]
  198. instance HasAttributes (TextAreaConfig t) where
  199. type Attrs (TextAreaConfig t) = Dynamic t (Map String String)
  200. attributes = textAreaConfig_attributes
  201. instance HasAttributes (TextInputConfig t) where
  202. type Attrs (TextInputConfig t) = Dynamic t (Map String String)
  203. attributes = textInputConfig_attributes
  204. instance HasAttributes (DropdownConfig t k) where
  205. type Attrs (DropdownConfig t k) = Dynamic t (Map String String)
  206. attributes = dropdownConfig_attributes
  207. instance HasAttributes (CheckboxConfig t) where
  208. type Attrs (CheckboxConfig t) = Dynamic t (Map String String)
  209. attributes = checkboxConfig_attributes
  210. class HasSetValue a where
  211. type SetValue a :: *
  212. setValue :: Lens' a (SetValue a)
  213. instance HasSetValue (TextAreaConfig t) where
  214. type SetValue (TextAreaConfig t) = Event t String
  215. setValue = textAreaConfig_setValue
  216. instance HasSetValue (TextInputConfig t) where
  217. type SetValue (TextInputConfig t) = Event t String
  218. setValue = textInputConfig_setValue
  219. instance HasSetValue (DropdownConfig t k) where
  220. type SetValue (DropdownConfig t k) = Event t k
  221. setValue = dropdownConfig_setValue
  222. instance HasSetValue (CheckboxConfig t) where
  223. type SetValue (CheckboxConfig t) = Event t Bool
  224. setValue = checkboxConfig_setValue
  225. class HasValue a where
  226. type Value a :: *
  227. value :: a -> Value a
  228. instance HasValue (TextArea t) where
  229. type Value (TextArea t) = Dynamic t String
  230. value = _textArea_value
  231. instance HasValue (TextInput t) where
  232. type Value (TextInput t) = Dynamic t String
  233. value = _textInput_value
  234. instance HasValue (FileInput t) where
  235. type Value (FileInput t) = Dynamic t [File]
  236. value = _fileInput_value
  237. instance HasValue (Dropdown t k) where
  238. type Value (Dropdown t k) = Dynamic t k
  239. value = _dropdown_value
  240. instance HasValue (Checkbox t) where
  241. type Value (Checkbox t) = Dynamic t Bool
  242. value = _checkbox_value
  243. {-
  244. type family Controller sm t a where
  245. Controller Edit t a = (a, Event t a) -- Initial value and setter
  246. Controller View t a = Dynamic t a -- Value (always)
  247. type family Output sm t a where
  248. Output Edit t a = Dynamic t a -- Value (always)
  249. Output View t a = Event t a -- Requested changes
  250. data CheckboxConfig sm t
  251. = CheckboxConfig { _checkbox_input :: Controller sm t Bool
  252. , _checkbox_attributes :: Attributes
  253. }
  254. instance Reflex t => Default (CheckboxConfig Edit t) where
  255. def = CheckboxConfig (False, never) mempty
  256. data Checkbox sm t
  257. = Checkbox { _checkbox_output :: Output sm t Bool
  258. }
  259. data StateMode = Edit | View
  260. --TODO: There must be a more generic way to get this witness and allow us to case on the type-level StateMode
  261. data StateModeWitness (sm :: StateMode) where
  262. EditWitness :: StateModeWitness Edit
  263. ViewWitness :: StateModeWitness View
  264. class HasStateModeWitness (sm :: StateMode) where
  265. stateModeWitness :: StateModeWitness sm
  266. instance HasStateModeWitness Edit where
  267. stateModeWitness = EditWitness
  268. instance HasStateModeWitness View where
  269. stateModeWitness = ViewWitness
  270. -}