Wire up Err for client-side validations.

This commit is contained in:
Dillon Kearns 2022-01-06 07:57:16 -08:00
parent c747893453
commit e22d688920
4 changed files with 77 additions and 40 deletions

View File

@ -188,7 +188,7 @@ page =
type alias Data =
{ user : Maybe User
{ user : Maybe (Result String User)
, errors : Maybe (Dict String { raw : Maybe String, errors : List String })
}
@ -255,21 +255,25 @@ view maybeUrl sharedModel static =
user : User
user =
static.data.user
|> Maybe.withDefault defaultUser
|> Maybe.withDefault (Ok defaultUser)
|> Result.withDefault defaultUser
in
{ title = "Form Example"
, body =
[ static.data.user
|> Maybe.map
(\user_ ->
Html.p
[ Attr.style "padding" "10px"
, Attr.style "background-color" "#a3fba3"
]
[ Html.text <| "Successfully received user " ++ user_.first ++ " " ++ user_.last
]
(Result.map
(\user_ ->
Html.p
[ Attr.style "padding" "10px"
, Attr.style "background-color" "#a3fba3"
]
[ Html.text <| "Successfully received user " ++ user_.first ++ " " ++ user_.last
]
)
)
|> Maybe.withDefault (Html.p [] [])
|> Maybe.withDefault (Err "")
|> Result.withDefault (Html.p [] [])
, Html.h1
[]
[ Html.text <| "Edit profile " ++ user.first ++ " " ++ user.last ]

View File

@ -420,7 +420,7 @@ init _ _ static =
type alias Data =
{ user : Maybe User
{ user : Maybe (Result String User)
, errors : Maybe (Dict String { raw : Maybe String, errors : List String })
}
@ -533,7 +533,8 @@ view maybeUrl sharedModel model static =
user : User
user =
static.data.user
|> Maybe.withDefault defaultUser
|> Maybe.withDefault (Ok defaultUser)
|> Result.withDefault defaultUser
in
{ title = "Form Example"
, body =
@ -543,17 +544,20 @@ view maybeUrl sharedModel model static =
, formModelView model.form
, static.data.user
|> Maybe.map
(\user_ ->
Html.p
[ css
[ Css.backgroundColor (Css.rgb 163 251 163)
, Tw.p_4
(Result.map
(\user_ ->
Html.p
[ css
[ Css.backgroundColor (Css.rgb 163 251 163)
, Tw.p_4
]
]
]
[ Html.text <| "Successfully received user " ++ user_.first ++ " " ++ user_.last
]
[ Html.text <| "Successfully received user " ++ user_.first ++ " " ++ user_.last
]
)
)
|> Maybe.withDefault (Html.p [] [])
|> Maybe.withDefault (Err "")
|> Result.withDefault (Html.p [] [])
, Html.div
[ css
[ Tw.flex

View File

@ -13,12 +13,14 @@ import Server.Request as Request exposing (Request)
type Form value view
= Form
-- TODO either make this a Dict and include the client-side validations here
-- OR create a new Dict with ( name => client-side validation ( name -> Result String () )
(List
( List (FieldInfoSimple view)
, List view -> List view
)
)
(Request value)
(Request (Result String value))
(Request
(DataSource
(List
@ -48,6 +50,7 @@ type alias FieldInfoSimple view =
-> Maybe { raw : Maybe String, errors : List String }
-> view
, properties : List ( String, Encode.Value )
, clientValidations : Maybe String -> Result String ()
}
@ -61,7 +64,7 @@ type alias FieldInfo value view =
FinalFieldInfo
-> Maybe { raw : Maybe String, errors : List String }
-> view
, decode : Maybe String -> value
, decode : Maybe String -> Result String value
, properties : List ( String, Encode.Value )
}
@ -79,7 +82,7 @@ type alias FinalFieldInfo =
succeed : constructor -> Form constructor view
succeed constructor =
Form []
(Request.succeed constructor)
(Request.succeed (Ok constructor))
(Request.succeed (DataSource.succeed []))
(\_ -> Ok constructor)
@ -283,7 +286,7 @@ text name toHtmlFn =
toHtmlFn (toInputRecord name Nothing info fieldInfo)
-- TODO should it be Err if Nothing?
, decode = Maybe.withDefault ""
, decode = Maybe.withDefault "" >> Ok
, properties = []
}
@ -308,7 +311,7 @@ hidden name value toHtmlFn =
toHtmlFn (toInputRecord name Nothing info fieldInfo |> .toInput)
-- TODO should it be Err if Nothing?
, decode = Maybe.withDefault ""
, decode = Maybe.withDefault "" >> Ok
, properties = []
}
@ -375,6 +378,7 @@ radio name nonEmptyItemMapping toHtmlFn wrapFn =
\raw ->
raw
|> Maybe.andThen fromString
|> Ok
, properties = []
}
@ -414,7 +418,7 @@ submit toHtmlFn =
{ attrs =
[ Attr.type_ "submit" ]
}
, decode = \_ -> ()
, decode = \_ -> Ok ()
, properties = []
}
@ -432,7 +436,7 @@ view viewFn =
, toHtml =
\fieldInfo info ->
viewFn
, decode = \_ -> ()
, decode = \_ -> Ok ()
, properties = []
}
@ -461,6 +465,7 @@ number name toHtmlFn =
\rawString ->
rawString
|> Maybe.andThen String.toInt
|> Ok
, properties = []
}
@ -489,8 +494,8 @@ requiredNumber name toHtmlFn =
\rawString ->
rawString
|> Maybe.andThen String.toInt
-- TODO this needs to be a Result that can be decoded
|> Maybe.withDefault -1000
-- TODO should this be a custom type instead of String error? That way users can customize the error messages
|> Result.fromMaybe "Not a valid number"
, properties = []
}
@ -520,8 +525,8 @@ date name toHtmlFn =
\rawString ->
rawString
|> Maybe.withDefault ""
-- TODO should empty string be decoded into Nothing instead of an error?
|> Date.fromIsoString
|> Result.withDefault (Date.fromRataDie 0)
, properties = []
}
@ -555,7 +560,7 @@ checkbox name initial toHtmlFn =
toHtmlFn (toInputRecord name Nothing info fieldInfo)
, decode =
\rawString ->
rawString == Just "on"
Ok (rawString == Just "on")
, properties = []
}
@ -645,7 +650,14 @@ withServerValidation : (value -> DataSource (List String)) -> Field value view -
withServerValidation serverValidation (Field field) =
Field
{ field
| serverValidation = field.decode >> serverValidation
| serverValidation =
\value ->
case value |> field.decode of
Ok decoded ->
serverValidation decoded
Err error ->
DataSource.fail <| "Could not decode form data: " ++ error
}
@ -674,8 +686,10 @@ with (Field field) (Form fields decoder serverValidations modelToValue) =
in
Form
(addField field fields)
(decoder
|> Request.andMap (Request.optionalFormField_ field.name |> Request.map field.decode)
(Request.map2
(Result.map2 (|>))
(Request.optionalFormField_ field.name |> Request.map field.decode)
decoder
)
thing
(\model ->
@ -693,8 +707,9 @@ with (Field field) (Form fields decoder serverValidations modelToValue) =
Ok okSoFar ->
maybeValue
|> field.decode
|> okSoFar
|> Ok
-- TODO have a `List String` for field validation errors, too
|> Result.mapError List.singleton
|> Result.andThen (okSoFar >> Ok)
)
@ -724,7 +739,7 @@ appendForm mapFn (Form fields1 decoder1 serverValidations1 modelToValue1) (Form
Form
-- TODO is this ordering correct?
(fields1 ++ fields2)
(Request.map2 mapFn decoder1 decoder2)
(Request.map2 (Result.map2 mapFn) decoder1 decoder2)
(Request.map2
(DataSource.map2 (++))
serverValidations1
@ -786,6 +801,7 @@ simplify2 field =
, serverValidation = field.serverValidation
, toHtml = field.toHtml
, properties = field.properties
, clientValidations = \value -> value |> field.decode |> Result.map (\_ -> ())
}
@ -838,7 +854,7 @@ toHtml toForm serverValidationErrors (Form fields decoder serverValidations mode
)
toRequest : Form value view -> Request value
toRequest : Form value view -> Request (Result String value)
toRequest (Form fields decoder serverValidations modelToValue) =
Request.expectFormPost
(\_ ->
@ -851,7 +867,7 @@ toRequest2 :
->
Request
(DataSource
(Result Model ( value, Model ))
(Result Model ( Result String value, Model ))
)
toRequest2 (Form fields decoder serverValidations modelToValue) =
Request.map2

View File

@ -27,6 +27,19 @@ all =
)
|> Expect.equal
(Ok "Jane")
, test "run a single field's validation on blur" <|
\() ->
Form.succeed identity
|> Form.with (Form.date "dob" toInput)
|> Form.runClientValidations
(Dict.fromList
[ ( "dob"
, { raw = Just "This is not a valid date", errors = [] }
)
]
)
|> Expect.equal
(Err [ "Expected a date in ISO 8601 format" ])
]