/example/external-components/Form.purs

https://github.com/thomashoneyman/purescript-halogen-formless · PureScript · 202 lines · 169 code · 26 blank · 7 comment · 7 complexity · 0c704c71d1ae8c4a60ed6c92b208610b MD5 · raw file

  1. module Example.ExternalComponents.Form where
  2. import Prelude
  3. import Data.Const (Const)
  4. import Data.Maybe (Maybe(..), fromMaybe)
  5. import Data.Newtype (class Newtype)
  6. import Effect.Aff (Aff)
  7. import Effect.Class.Console (logShow)
  8. import Example.App.UI.Element as UI
  9. import Example.App.UI.Typeahead as TA
  10. import Example.App.Validation as V
  11. import Formless as F
  12. import Halogen as H
  13. import Halogen.HTML as HH
  14. import Halogen.HTML.Events as HE
  15. import Halogen.HTML.Properties as HP
  16. import Record (delete)
  17. import Select as Select
  18. import Type.Proxy (Proxy(..))
  19. -- Form spec
  20. -- equivalent to { name :: String, email :: V.Email, ... }
  21. type User = { | UserFormRow F.OutputType }
  22. newtype UserForm (r :: Row Type -> Type) f = UserForm (r (UserFormRow f))
  23. derive instance newtypeUserForm' :: Newtype (UserForm r f) _
  24. type UserFormRow :: (Type -> Type -> Type -> Type) -> Row Type
  25. type UserFormRow f =
  26. ( name :: f V.FieldError String String
  27. , email :: f V.FieldError (Maybe String) V.Email
  28. , whiskey :: f V.FieldError (Maybe String) String
  29. , language :: f V.FieldError (Maybe String) String
  30. )
  31. -- Form component types
  32. data Action
  33. = HandleTypeahead Typeahead (TA.Message Maybe String)
  34. | Reset
  35. -- Form child component types
  36. type ChildSlots =
  37. (typeahead :: TA.Slot Maybe String Typeahead)
  38. data Typeahead
  39. = Email
  40. | Whiskey
  41. | Language
  42. derive instance eqTypeahead :: Eq Typeahead
  43. derive instance ordTypeahead :: Ord Typeahead
  44. -- Component spec
  45. component :: F.Component UserForm (Const Void) ChildSlots Unit User Aff
  46. component = F.component (const defaultInput) $ F.defaultSpec
  47. { render = render
  48. , handleAction = handleAction
  49. , handleEvent = handleEvent
  50. }
  51. where
  52. defaultInput :: F.Input' UserForm Aff
  53. defaultInput =
  54. { validators: UserForm
  55. { name: V.minLength 7
  56. , email: V.exists >>> V.emailFormat
  57. , whiskey: V.exists
  58. , language: V.exists
  59. }
  60. , initialInputs: Nothing
  61. }
  62. handleEvent = case _ of
  63. F.Submitted outputs -> H.raise (F.unwrapOutputFields outputs)
  64. F.Changed formState -> logShow $ delete (Proxy :: _ "form") formState
  65. prx = F.mkSProxies (Proxy :: Proxy UserForm)
  66. handleAction = case _ of
  67. HandleTypeahead slot (TA.SelectionsChanged new) -> case slot of
  68. Email ->
  69. eval $ F.setValidate prx.email new
  70. Whiskey -> do
  71. eval $ F.setValidate prx.whiskey new
  72. eval $ F.reset prx.email
  73. void $ H.query TA._typeahead Email TA.clear
  74. Language -> do
  75. eval $ F.setValidate prx.language new
  76. Reset -> do
  77. items <- H.request TA._typeahead Email TA.getAvailableItems
  78. logShow $ fromMaybe [] items
  79. _ <- H.queryAll TA._typeahead TA.clear
  80. eval F.resetAll
  81. where
  82. -- you will usually want to define this pre-applied function if you
  83. -- are recursively evaluating Formless actions.
  84. eval act = F.handleAction handleAction handleEvent act
  85. render :: F.PublicState UserForm () -> F.ComponentHTML UserForm Action ChildSlots Aff
  86. render st =
  87. UI.formContent_
  88. [ name
  89. , email
  90. , whiskey
  91. , language
  92. , UI.p_
  93. """
  94. You can only attempt to submit this form if it is valid and not already being submitted. You can only attempt to reset the form if it has changed from its initial state.
  95. """
  96. , HH.br_
  97. , UI.grouped_
  98. [ UI.buttonPrimary
  99. [ if st.submitting || st.validity /= F.Valid then HP.disabled true
  100. else HE.onClick \_ -> F.submit
  101. ]
  102. [ HH.text "Submit" ]
  103. , UI.button
  104. [ if not st.dirty then HP.disabled true
  105. else HE.onClick \_ -> F.injAction Reset
  106. ]
  107. [ HH.text "Reset" ]
  108. ]
  109. ]
  110. where
  111. name = st # UI.formlessField UI.input
  112. { label: "Name"
  113. , help: "Write your name"
  114. , placeholder: "Dale"
  115. , sym: prx.name
  116. }
  117. email = UI.field
  118. { label: "Email"
  119. , help: F.getResult prx.email st.form # UI.resultToHelp "Choose an email"
  120. }
  121. [ singleTypeahead Email
  122. { placeholder: "me@you.com"
  123. , items:
  124. [ "not@anemail.org"
  125. , "snail@utopia.snailutopia"
  126. , "blue@jordans@blordens.pordens"
  127. , "yea_that_won't_work@email.com"
  128. , "standard@email.com"
  129. ]
  130. }
  131. ]
  132. whiskey = UI.field
  133. { label: "Whiskey"
  134. , help: F.getResult prx.whiskey st.form # UI.resultToHelp
  135. "Select a favorite whiskey"
  136. }
  137. [ singleTypeahead Whiskey
  138. { placeholder: "Lagavulin 12"
  139. , items:
  140. [ "Lagavulin 16"
  141. , "Kilchoman Blue Label"
  142. , "Laphroaig"
  143. , "Ardbeg"
  144. ]
  145. }
  146. ]
  147. language = UI.field
  148. { label: "Language"
  149. , help: F.getResult prx.language st.form # UI.resultToHelp
  150. "Choose your favorite programming language"
  151. }
  152. [ singleTypeahead Language
  153. { placeholder: "Haskell"
  154. , items:
  155. [ "Rust"
  156. , "Python"
  157. , "Blodwen"
  158. , "Hackett"
  159. , "PHP"
  160. , "PureScript"
  161. , "JavaScript"
  162. , "C"
  163. , "C++"
  164. , "TLA+"
  165. , "F#"
  166. , "F*"
  167. , "Agda"
  168. , "Ruby"
  169. , "APL"
  170. ]
  171. }
  172. ]
  173. singleTypeahead slot input =
  174. HH.slot TA._typeahead slot (Select.component TA.input TA.single) input handler
  175. where
  176. handler = F.injAction <<< HandleTypeahead slot