From e0423938306393385f95fa4768da2cb113889df8 Mon Sep 17 00:00:00 2001 From: Dillon Kearns Date: Thu, 6 Jan 2022 09:44:46 -0800 Subject: [PATCH] Add function to include client-side validation. --- examples/pokedex/src/Page/TailwindForm.elm | 10 +++- src/Form.elm | 57 ++++++++++++++++++++-- tests/FormTests.elm | 23 +++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/examples/pokedex/src/Page/TailwindForm.elm b/examples/pokedex/src/Page/TailwindForm.elm index 7c7b6b89..8839e5cb 100644 --- a/examples/pokedex/src/Page/TailwindForm.elm +++ b/examples/pokedex/src/Page/TailwindForm.elm @@ -207,6 +207,14 @@ form user = "first" (textInput "First name") |> Form.withInitialValue user.first + |> Form.withClientValidation + (\first -> + if first |> String.toList |> List.head |> Maybe.withDefault 'a' |> Char.isUpper then + Ok first + + else + Err "Needs to be capitalized" + ) |> Form.required ) |> Form.with @@ -412,7 +420,7 @@ page = update _ _ _ _ msg model = case msg of FormMsg formMsg -> - ( { model | form = model.form |> Form.update formMsg }, Cmd.none ) + ( { model | form = model.form |> Form.update (form defaultUser) formMsg }, Cmd.none ) init _ _ static = diff --git a/src/Form.elm b/src/Form.elm index a6526c43..aacdc005 100644 --- a/src/Form.elm +++ b/src/Form.elm @@ -7,6 +7,7 @@ import Html exposing (Html) import Html.Attributes as Attr import Html.Events import Json.Encode as Encode +import List.Extra import List.NonEmpty import Server.Request as Request exposing (Request) @@ -102,8 +103,40 @@ type alias Model = Dict String { raw : Maybe String, errors : List String } -update : Msg -> Model -> Model -update msg model = +runValidation : Form value view -> { name : String, value : String } -> List String +runValidation (Form fields decoder serverValidations modelToValue) newInput = + let + matchingDecoder : Maybe (FieldInfoSimple view) + matchingDecoder = + fields + |> List.Extra.findMap + (\( fields_, _ ) -> + List.Extra.findMap + (\field -> + if field.name == newInput.name then + Just field + + else + Nothing + ) + fields_ + ) + in + case matchingDecoder of + Just decoder_ -> + case decoder_.clientValidations (Just newInput.value) of + Ok () -> + [] + + Err error -> + [ error ] + + Nothing -> + [] + + +update : Form value view -> Msg -> Model -> Model +update form msg model = case msg of OnFieldInput { name, value } -> -- TODO run client-side validations @@ -113,7 +146,7 @@ update msg model = case entry of Just { raw, errors } -> -- TODO calculate errors here? - Just { raw = Just value, errors = errors } + Just { raw = Just value, errors = runValidation form { name = name, value = value } } Nothing -> -- TODO calculate errors here? @@ -661,6 +694,24 @@ withServerValidation serverValidation (Field field) = } +withClientValidation : (value -> Result String mapped) -> Field value view -> Field mapped view +withClientValidation mapFn (Field field) = + Field + { name = field.name + , initialValue = field.initialValue + , type_ = field.type_ + , required = field.required + , serverValidation = field.serverValidation + , toHtml = field.toHtml + , decode = + \value -> + value + |> field.decode + |> Result.andThen mapFn + , properties = field.properties + } + + with : Field value view -> Form (value -> form) view -> Form form view with (Field field) (Form fields decoder serverValidations modelToValue) = let diff --git a/tests/FormTests.elm b/tests/FormTests.elm index 696c91ba..a91f894b 100644 --- a/tests/FormTests.elm +++ b/tests/FormTests.elm @@ -40,6 +40,29 @@ all = ) |> Expect.equal (Err [ "Expected a date in ISO 8601 format" ]) + , test "custom client validation" <| + \() -> + Form.succeed identity + |> Form.with + (Form.text "first" toInput + |> Form.withClientValidation + (\first -> + if first |> String.toList |> List.head |> Maybe.withDefault 'a' |> Char.isUpper then + Ok first + + else + Err "Needs to be capitalized" + ) + ) + |> Form.runClientValidations + (Dict.fromList + [ ( "first" + , { raw = Just "jane", errors = [] } + ) + ] + ) + |> Expect.equal + (Err [ "Needs to be capitalized" ]) ]