Skip to content

Commit 657225e

Browse files
Add submitPreventDefault to Formless when using forms (#80)
Co-authored-by: Thomas Honeyman <[email protected]>
1 parent 3f6ffef commit 657225e

File tree

8 files changed

+145
-6
lines changed

8 files changed

+145
-6
lines changed

example/Main.purs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Effect (Effect)
88
import Effect.Aff (Aff)
99
import Example.Basic.Component as Basic
1010
import Example.Async.Component as Async
11+
import Example.Readme.Component as Readme
1112
import Example.Nested.Page as Nested
1213
import Example.ExternalComponents.Page as ExternalComponents
1314
import Example.App.Home as Home
@@ -25,6 +26,7 @@ stories = Object.fromFoldable
2526
, Tuple "async" $ proxy Async.component
2627
, Tuple "nested" $ proxy Nested.component
2728
, Tuple "real-world" $ proxy RealWorld.component
29+
, Tuple "readme" $ proxy Readme.component
2830
]
2931

3032
main :: Effect Unit

example/readme/Component.purs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
-- | This example component shows how submitPreventDefault works in Halogen Formless
2+
module Example.Readme.Component (component) where
3+
4+
import Prelude
5+
6+
import Data.Either (Either(..))
7+
import Data.Int as Int
8+
import Data.Maybe (Maybe(..))
9+
import Data.Newtype (class Newtype, unwrap)
10+
import Effect.Aff.Class (class MonadAff)
11+
import Effect.Class.Console (logShow)
12+
import Example.App.UI.Element as UI
13+
import Example.App.Validation (class ToText)
14+
import Formless as F
15+
import Halogen as H
16+
import Halogen.HTML as HH
17+
import Halogen.HTML.Events as HE
18+
import Halogen.HTML.Properties as HP
19+
import Type.Proxy (Proxy(..))
20+
21+
type Dog = { name :: String, age :: Age }
22+
23+
newtype Age = Age Int
24+
25+
derive instance newtypeAge :: Newtype Age _
26+
27+
instance showAge :: Show Age where
28+
show = show <<< unwrap
29+
30+
data AgeError = TooLow | TooHigh | InvalidInt
31+
32+
newtype DogForm (r :: Row Type -> Type) f = DogForm (r
33+
-- error input output
34+
( name :: f Void String String
35+
, age :: f AgeError String Age
36+
))
37+
38+
derive instance newtypeDogForm :: Newtype (DogForm r f) _
39+
40+
instance ToText AgeError where
41+
toText = case _ of
42+
InvalidInt -> "Age must be an integer"
43+
TooLow -> "Age cannot be negative"
44+
TooHigh -> "No dog has lived past 30 before"
45+
46+
input :: forall m. Monad m => F.Input' DogForm m
47+
input =
48+
{ initialInputs: Nothing -- same as: Just (F.wrapInputFields { name: "", age: "" })
49+
, validators: DogForm
50+
{ name: F.noValidation
51+
, age: F.hoistFnE_ \str -> case Int.fromString str of
52+
Nothing -> Left InvalidInt
53+
Just n
54+
| n < 0 -> Left TooLow
55+
| n > 30 -> Left TooHigh
56+
| otherwise -> Right (Age n)
57+
}
58+
}
59+
60+
spec :: forall input m. Monad m => F.Spec' DogForm Dog input m
61+
spec = F.defaultSpec { render = render, handleEvent = F.raiseResult }
62+
where
63+
render st@{ form } =
64+
UI.formContent_
65+
[ HH.form
66+
[ -- Using a form forces us to deal with an event. Using '\_ -> F.submit' here
67+
-- would fire the event and cause the page to reload. Instead, we use
68+
-- 'F.submitPreventDefault' to avoid firing the event unnecessarily
69+
HE.onSubmit F.submitPreventDefault
70+
]
71+
[ UI.input
72+
{ label: "Name"
73+
, help: Right "Write your dog's name"
74+
, placeholder: "Mila"
75+
}
76+
[ HP.value $ F.getInput _name st.form
77+
, HE.onValueInput (F.setValidate _name)
78+
]
79+
, UI.input
80+
{ label: "Age"
81+
, help: UI.resultToHelp "Write your dog's age" $ F.getResult _age st.form
82+
, placeholder: "3"
83+
}
84+
[ HP.value $ F.getInput _age form
85+
, HE.onValueInput $ F.setValidate _age
86+
]
87+
, UI.buttonPrimary
88+
[]
89+
[ HH.text "Submit" ]
90+
]
91+
]
92+
where
93+
_name = Proxy :: Proxy "name"
94+
_age = Proxy :: Proxy "age"
95+
96+
data Action = HandleDogForm Dog
97+
98+
component :: forall q i o m. MonadAff m => H.Component q i o m
99+
component = H.mkComponent
100+
{ initialState: const unit
101+
, render: const render
102+
, eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
103+
}
104+
where
105+
handleAction (HandleDogForm dog) = logShow (dog :: Dog)
106+
107+
render =
108+
UI.section_
109+
[ UI.h1_ [ HH.text "Formless" ]
110+
, UI.h2_ [ HH.text "The form from the readme" ]
111+
, HH.slot F._formless unit (F.component (const input) spec) unit HandleDogForm
112+
]

readme.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ spec :: forall input m. Monad m => F.Spec' DogForm Dog input m
9191
spec = F.defaultSpec { render = render, handleEvent = F.raiseResult }
9292
where
9393
render st@{ form } =
94-
HH.form_
94+
HH.form
95+
[ HE.onSubmit F.submitPreventDefault
96+
]
9597
[ HH.input
9698
[ HP.value $ F.getInput _name form
9799
, HE.onValueInput $ F.set _name
@@ -105,8 +107,7 @@ spec = F.defaultSpec { render = render, handleEvent = F.raiseResult }
105107
Just InvalidInt -> "Age must be an integer"
106108
Just TooLow -> "Age cannot be negative"
107109
Just TooHigh -> "No dog has lived past 30 before"
108-
, HH.button
109-
[ HE.onClick \_ -> F.submit ]
110+
, HH.button_
110111
[ HH.text "Submit" ]
111112
]
112113
where

spago.dhall

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
, "typelevel-prelude"
2525
, "unsafe-coerce"
2626
, "variant"
27+
, "web-events"
2728
]
2829
, packages = ./packages.dhall
2930
, sources = [ "src/**/*.purs" ]

src/Formless.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module Formless
2020
, module Formless.Validation
2121
) where
2222

23-
import Formless.Action (asyncModifyValidate, asyncSetValidate, injAction, loadForm, modify, modifyAll, modifyValidate, modifyValidateAll, reset, resetAll, set, setAll, setValidate, setValidateAll, submit, validate, validateAll)
23+
import Formless.Action (asyncModifyValidate, asyncSetValidate, injAction, loadForm, modify, modifyAll, modifyValidate, modifyValidateAll, reset, resetAll, set, setAll, setValidate, setValidateAll, submit, submitPreventDefault, validate, validateAll)
2424
import Formless.Class.Initial (class Initial, initial)
2525
import Formless.Component (component, defaultSpec, handleAction, handleQuery, raiseResult)
2626
import Formless.Data.FormFieldResult (FormFieldResult(..), _Error, _Success, fromEither, toMaybe)

src/Formless/Action.purs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Formless.Types.Form (InputField, InputFunction, U(..))
1919
import Heterogeneous.Mapping as HM
2020
import Prim.Row as Row
2121
import Type.Proxy (Proxy(..))
22+
import Web.Event.Event as Event
2223

2324
-- | Inject your own action into the Formless component so it can be used in HTML
2425
injAction :: forall form act. act -> Action form act
@@ -264,8 +265,9 @@ resetAll :: forall v. Variant (resetAll :: Unit | v)
264265
resetAll =
265266
inj (Proxy :: _ "resetAll") unit
266267

267-
-- | Submit the form, which will trigger a `Submitted` result if the
268-
-- | form validates successfully.
268+
-- | Submit the form, which will trigger a `Submitted` result if the form
269+
-- | validates successfully. If you want to capture the form submission event
270+
-- | and submit your form use `submitPreventDefault`.
269271
-- |
270272
-- | ```purescript
271273
-- | [ HE.onClick \_ -> Just F.submit ]
@@ -274,6 +276,20 @@ submit :: forall v. Variant (submit :: Unit | v)
274276
submit =
275277
inj (Proxy :: _ "submit") unit
276278

279+
-- | Submit the form, calling `preventDefault` from `Web.Event.Event` on the
280+
-- | submission event to prevent the browser from refreshing the page.
281+
-- |
282+
-- | ```purescript
283+
-- | HH.form
284+
-- | [ HE.onSubmit F.submitPreventDefault ]
285+
-- | [ ... ]
286+
-- | ```
287+
submitPreventDefault
288+
:: forall v
289+
. Event.Event
290+
-> Variant (submitPreventDefault :: Event.Event | v)
291+
submitPreventDefault = inj (Proxy :: _ "submitPreventDefault")
292+
277293
-- | Load a form from a set of existing inputs. Useful for when you need to mount
278294
-- | Formless, perform some other actions like request data from the server, and
279295
-- | then load an existing set of inputs.

src/Formless/Component.purs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import Prim.RowList as RL
3030
import Record.Builder as Builder
3131
import Type.Proxy (Proxy(..))
3232
import Unsafe.Coerce (unsafeCoerce)
33+
import Web.Event.Event as Event
3334

3435
-- | The default spec, which can be overridden by whatever functions you need
3536
-- | to extend the component. For example:
@@ -321,6 +322,10 @@ handleAction handleAction' handleEvent action = flip match action
321322
_ <- handleAction handleAction' handleEvent FA.validateAll
322323
IC.submit >>= traverse_ (Submitted >>> handleEvent)
323324

325+
, submitPreventDefault: \event -> do
326+
H.liftEffect $ Event.preventDefault event
327+
handleAction handleAction' handleEvent FA.submit
328+
324329
, loadForm: \formInputs -> do
325330
let setFields rec = rec { allTouched = false, initialInputs = formInputs }
326331
st <- H.get

src/Formless/Types/Component.purs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Halogen.Query.ChildQuery (ChildQueryBox)
1818
import Halogen.Query.HalogenM (ForkId)
1919
import Type.Proxy (Proxy(..))
2020
import Type.Row (type (+))
21+
import Web.Event.Event as Event
2122

2223
-- | A type representing the various functions that can be provided to extend
2324
-- | the Formless component. Usually only the `render` function is required,
@@ -62,6 +63,7 @@ type PublicAction form =
6263
, validateAll :: Unit
6364
, resetAll :: Unit
6465
, submit :: Unit
66+
, submitPreventDefault :: Event.Event
6567
, loadForm :: form Record InputField
6668
)
6769

0 commit comments

Comments
 (0)