Using Dict.Any and a few helper functions to manage the state of a form
type alias Model =
{ userForm : FormData UserFields User
}
type alias User =
{ firstname : String
, hobbies : List Hobby
}
type UserFields
= Firstname
| Hobbies Hobby
type Hobby = Soccer | Football | Basketball
stringHobby : Hobby -> String
stringHobby hobby =
case hobby of
Soccer -> "soccer"
Football -> "football"
Basketball -> "basketball"
stringUserFields : UserFields -> String
stringUserFields field =
case field of
Firstname ->
"firstname"
Hobbies h ->
"hobbies " ++ stringHobby h
model : Model
model =
{ userForm = FormData.init stringUserFields []
}
currentForm : FormData UserFields User
currentForm =
model.userForm
|> onInput Firstname "Alice"
|> onCheck (Hobbies Soccer) True
|> onCheck (Hobbies Football) True
|> onCheck (Hobbies Soccer) False
|> onCheck (Hobbies Basketball) True
currentForm
--> FormData.init stringUserFields
--> [ (Firstname, "Alice"), (Hobbies Basketball, ""), (Hobbies Football, "") ]
parseDontValidate : List (UserFields, String) -> ( Maybe User, List ( Maybe UserFields, String ) )
parseDontValidate keyValueList =
let
initalUserWithErrors =
( { firstname = "", hobbies = [] }
, [ ( Just Firstname, "cannot be blank")
, ( Nothing, "must choose one hobby" )
]
)
--
buildUserErrs (k, s) (partUser, partErrs) =
case k of
Firstname ->
( { partUser | firstname = s }
, if s /= "" then
List.filter (\(maybeK, _) -> maybeK /= Just k) partErrs
else
partErrs
)
Hobbies h ->
( { partUser | hobbies = h :: partUser.hobbies }
, List.filter (\(maybeK, _) -> maybeK /= Nothing) partErrs
)
--
(value, errs) =
List.foldl buildUserErrs initalUserWithErrors keyValueList
in
if [] == errs then
(Just { value | hobbies = List.reverse value.hobbies }, [])
else
(Nothing, errs)
model.userForm
|> FormData.parse parseDontValidate
--> ( Nothing , errorsFrom stringUserFields [ ( Just Firstname, "cannot be blank"), ( Nothing, "must choose one hobby") ] )
model.userForm
|> FormData.onInput Firstname "Alice"
|> FormData.parse parseDontValidate
--> ( Nothing , errorsFrom stringUserFields [ ( Nothing, "must choose one hobby") ] )
model.userForm
|> FormData.onInput Firstname "Alice"
|> FormData.onCheck (Hobbies Football) True
|> FormData.parse parseDontValidate
--> ( Just { firstname = "Alice", hobbies = [Football] }, errorsFrom stringUserFields [] )The types parsed by Config will determine what types we are managing here
(k -> String)Note that it's important to make sure every key is turned to different comparable. Otherwise keys would conflict and overwrite each other.
Holds error values for faster lookup
Builds Errors value from List; used inside FormData.parse
What's the current value that we should set for form element of field k
view =
input
[ value (FormData.value Firstname formdata) ]
[]For handling onInput; updates the state based on incoming user input.
The incoming value is stored as-is
view formdata =
input
[ value (FormData.value Firstname formdata)
, onInput (OnUserFormInput Firstname)
]
[]
update msg model =
case msg of
OnUserFormInput k s ->
( { model | userForm = formdata.onInput k s model.userForm }
, Cmd.none
)Is String a chosen option for field k?
view formdata =
label []
[ text "Hobbies "
, input
[ checked (FormData.isChecked (Hobbies Basketball) formdata)
, type_ "checkbox"
]
[]
]For handling onCheck; stores multiple values for a single k
view formdata =
label []
[ text "Hobbies "
, input
[ checked (FormData.isChecked (Hobbies Basketball) formdata)
, onCheck (OnUserFormCheck (Hobbies Basketball))
, type_ "checkbox"
]
[]
]
update msg model =
case msg of
OnUserFormCheck k b ->
( { model | userForm = FormData.onCheck k b model.userForm }
, Cmd.none
)Before submitting, we should try to obtain a value (and a list of errors) from our FormData
( maybeUser, errors ) =
FormData.parse parseDontValidate model.userFormIf we get Nothing, disable the submit button. Otherwise, wire up the onSubmit handler
button
[ case maybeUser of
Just user ->
onSubmit (Save user)
Nothing ->
disabled True
]
[ text "Submit" ]If there's an error, show it
div
[ input [ onInput (OnInput Name), type_ "text", placeholder "Name" ] []
, case List.head (List.filter (\( k, v ) -> k == Just Name) errors) of
Just ( _, err ) ->
p [] [ small [] [ text err ] ]
Nothing ->
text ""
]in order to not show errors which user hasn't attempted, it would be nice to track which inputs were visited
can be used with onBlur to track which inputs were visited
view formdata =
input
[ onBlur (OnUserFormBlur Firstname)
]
[]
update msg model =
case msg of
OnUserFormBlur k ->
( { model | userForm = formdata.onVisited k model.userForm }
, Cmd.none
)inquire which inputs were visited
Filter Errors value against the visited tracker
The type that holds all the state.