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 type that holds all the state.
init : (k -> String) -> List ( k, String ) -> FormData k a
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.
The 3 states your data can be in
onSubmit : Basics.Bool -> FormData k a -> FormData k a
Toggles whether the FormData is submitting
Holds error values for faster lookup
errorsFrom : (k -> String) -> List ( Maybe k, err ) -> Errors k err
Builds Errors value from List; used inside FormData.parse
errorAt : Maybe k -> Errors k err -> Maybe err
Lookup if a Maybe k has error
value : k -> FormData k a -> String
What's the current value
that we should set for form element of field k
view =
input
[ value (FormData.value Firstname formdata) ]
[]
onInput : k -> String -> FormData k a -> FormData k a
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
)
isChecked : k -> FormData k a -> Basics.Bool
Is String
a chosen option for field k
?
view formdata =
label []
[ text "Hobbies "
, input
[ checked (FormData.isChecked (Hobbies Basketball) formdata)
, type_ "checkbox"
]
[]
]
onCheck : k -> Basics.Bool -> FormData k a -> FormData k a
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
)
parse : (List ( k, String ) -> ( Maybe a, List ( Maybe k, err ) )) -> FormData k a -> ( Data a, Errors k err )
Before submitting, we should try to obtain a value (and a list of errors) from our FormData
( maybeUser, errors ) =
FormData.parse parseDontValidate model.userForm
If 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
onVisited : Maybe k -> FormData k a -> FormData k a
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
)
hadVisited : Maybe k -> FormData k a -> Basics.Bool
inquire which inputs were visited
visitedErrors : FormData k a -> Errors k err -> Errors k err
Filter Errors value against the visited tracker
keyValues : FormData k a -> List ( k, String )
Returns the form state as a List
of (k, String)
tuple
This is the data that
init
functionparse
will supply to your parseDontValidate
function