Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upIntegrate Polyform into Ocelot #36
Conversation
thomashoneyman
added some commits
Apr 18, 2018
thomashoneyman
requested review from
davezuch
and
foresttoney
Apr 25, 2018
thomashoneyman
self-assigned this
Apr 25, 2018
thomashoneyman
requested a review
from
crcornwell
Apr 25, 2018
This comment has been minimized.
This comment has been minimized.
|
Compatible with and already merged with #35 |
This comment has been minimized.
This comment has been minimized.
|
@crcornwell is working on a way to generate the https://purescript-users.ml/t/generating-lenses-from-variants/54/9 I'm working on a class to generate the initial form state. With these in place, the boilerplate issue is pretty much solved. |
thomashoneyman
added some commits
Apr 27, 2018
This comment has been minimized.
This comment has been minimized.
|
Latest commits have eliminated the boilerplate around generating initial form state and the form inputs. All that remains is to get rid of the Once that's in, I'll add a snippet of code creating the form without comments. |
crcornwell
and others
added some commits
Apr 28, 2018
This comment has been minimized.
This comment has been minimized.
|
This is what creating a form might look like: signupForm :: ∀ eff. SignupForm (Aff eff)
signupForm = { email: _, password: _ }
<$> emailForm
<*> passwordForm
where
emailForm = formFromField _email $
hoistFnV validateNonEmptyStr
>>> hoistFnV (validateStrIsEmail "Not a valid email address.")
passwordForm = ( { p1: _, p2: _ }
<$> formFromField _p1 (hoistFnV validateNonEmptyStr)
<*> formFromField _p2 (hoistFnV validateNonEmptyStr)
)
>>> hoistFnV \{ p1, p2 } -> collapseIfEqual p1 p2 _p2
signupInitialForm :: FormInputs
signupInitialForm = makeDefaultFormInputs (RProxy :: RProxy (FormFieldsT Second))
type FormFieldsT f =
( email :: f EmailError String String
, p1 :: f PasswordError String String
, p2 :: f PasswordErrorEq String String
)
_email = SProxy :: SProxy "email"
_p1 = SProxy :: SProxy "p1"
_p2 = SProxy :: SProxy "p2"
type FieldValueV = Variant (FormFieldsT Second)
type FieldValidateV = Variant (FormFieldsT (K Boolean))
_form = SProxy :: SProxy "form"
updateValue :: FieldValueV -> (State -> State)
updateValue = modify _form <<< valueSetter (RProxy :: RProxy (FormFieldsT Second)) case_
updateValidate :: FieldValidateV -> (State -> State)
updateValidate = modify _form <<< validateSetter (RProxy :: RProxy (FormFieldsT (K Boolean))) case_
type FormInputs = Record (FormFieldsT (FormInput' FieldValueV FieldValidateV))
type FormFieldsOutT f =
( email :: f EmailError String String
, password :: f PasswordError String String
)
type FormOutputs = Record (FormFieldsOutT FormMaybe')
type SignupForm m = Form m FormInputs FormOutputsRouted through a component like this: data Query a
= UpdateContents FieldValueV a
| ValidateOne FieldValidateV a
| ValidateAll a
type State =
{ form :: FormInputs
, result :: Maybe { email :: String, password :: String }
}
component = ...
initialState :: State
initialState = { form: signupInitialForm, result: Nothing }
eval :: Query ~> H.ComponentDSL State Query Void (Aff ( console :: CONSOLE | eff))
eval = case _ of
UpdateContents val next -> do
H.modify $ updateValue val
pure next
ValidateOne val next -> do
H.modify $ updateValidate val
eval $ ValidateAll next
ValidateAll next -> do
st <- H.get
(Tuple form result) <- H.liftAff do
res <- runForm signupForm st.form
case res of
Valid form value -> do
pure $ Tuple form value
Invalid form -> do
pure $ Tuple form Nothing
H.modify _ { form = form, result = result }
pure next |
thomashoneyman
added
synced
and removed
breaking change
question
labels
Apr 28, 2018
This comment has been minimized.
This comment has been minimized.
|
@crcornwell @whoadave @foresttoney This branch is no longer a breaking change. Wildcat has been updated for compatibility and this can be merged into We should review on Monday and merge by EOD. |
crcornwell
reviewed
Apr 30, 2018
| @@ -1,39 +1,24 @@ | |||
| module Ocelot.Core.Utils | |||
| ( blockBuilder | |||
| module Ocelot.Properties | |||
This comment has been minimized.
This comment has been minimized.
| , setValue :: vl | ||
| , setValidate :: vd | ||
| , shouldValidate :: Boolean | ||
| } |
This comment has been minimized.
This comment has been minimized.
crcornwell
Apr 30, 2018
Contributor
value -> input
validated -> result
setValue -> setInput
shouldValidate -> validate
vl -> iv
vd -> vv
This comment has been minimized.
This comment has been minimized.
|
@crcornwell Need to verify that the name changes won't affect anything in Wildcat. I expect renaming |
thomashoneyman commentedApr 25, 2018
•
edited by crcornwell
This PR suggests a few changes to the way we develop static forms. This work integrates Polyform for composable, well-typed, static forms.
Why?
Additions
There is a reasonable argument to be made that most of these modules should move into Wildcat altogether so that Ocelot remains focused on being a design system. We would create a
Formmodule that contained our base functionality plus a collection of the various pre-built fields we'd like to compose into forms.Modules
src/Form/Form.pursThis module is the base of our static forms. It defines how to turn a field into a form and how forms and validation can compose together to create greater forms. It defines helpers for turning a row of types into most of the other form types we need via higher-kinded data / type synonyms. It also provides a
runFormfunction that, given a current state, some raw input, and our form transformation will produce a new form and maybe some valid, parsed, verified type as well.src/Form/Validation.pursThis module collects our validators together. They are almost identical to our old validation, except they use Polyform's
Vinstead ofpurescript-validation's type. These can be composed together.ui-guide/Utilities/Form.pursThis module contains a few pre-made fields with their attributes and errors already set. We'd most likely want to create a collection of these in Wildcat to avoid specifying them every time. We can build forms out of these fields via
formFromFieldand composition.Examples
test/Main.pursI have added a small test module to prove the approach. It can be deleted given there are now other examples, or it can be kept for small-scale testing without having to deal with rendering.
ui-guide/Components/Validation.pursA fully-built form and Halogen component that showcases how the forms work. This is the place to see how this would work in practice in Wildcat.
Other Changes
src/Blocks/FormField.pursThere is no giant
ValidationErrorssum type anymore. Instead, each field can have a variant of errors that need to be handled with amatchcase statement. This is nice because it means we can render much more fine-grained messages depending on where and when we are rendering a field, or we can always encode the error message into the variant and just unpack it. For that reason, I've changed this to just have anerrorfield that might contain a string or might not.Open Questions
Remaining Boilerplate
We still have places where we are writing boilerplate. Two major ones include:
updateValueandupdateValidatefunctions, which requires setting a symbol on every field in a record to a lens to itself. @crcornwell has experimented with generating these.Writing the initial form state. If we update to require a monad for fields, then we can use
memptyeverywhere and generate this one, too, but this isn't always possible (take something like a radio button).{ email: { value: "", shouldValidate: false } , password: { value: "", shouldValidate: false } }Tasks
updateValueandupdateValidatefunctions