From c5d9fe865dd51e8a7a363f364c70f6ea9369aa33 Mon Sep 17 00:00:00 2001 From: Dillon Kearns Date: Tue, 12 Jul 2022 12:25:14 +0200 Subject: [PATCH] Extract common helper for preparing validation errors and hidden input values in Form module. --- examples/pokedex/app/Route/Form.elm | 11 +- examples/pokedex/app/Route/Search.elm | 6 +- examples/pokedex/app/Route/Signup.elm | 6 +- examples/pokedex/app/Route/Todos.elm | 8 +- src/Form.elm | 199 ++++++++++---------------- 5 files changed, 96 insertions(+), 134 deletions(-) diff --git a/examples/pokedex/app/Route/Form.elm b/examples/pokedex/app/Route/Form.elm index d2396fe4..66ca4133 100644 --- a/examples/pokedex/app/Route/Form.elm +++ b/examples/pokedex/app/Route/Form.elm @@ -37,7 +37,7 @@ type alias RouteParams = type alias ActionData = - { user : User + { user : Maybe User , formResponse : Maybe { fields : List ( String, String ), errors : Dict String (List String) } } @@ -198,12 +198,12 @@ action routeParams = (\userResult -> (case userResult of Ok user -> - { user = user + { user = Just user , formResponse = Nothing } Err error -> - { user = defaultUser + { user = Nothing , formResponse = Just error } ) @@ -242,7 +242,7 @@ view maybeUrl sharedModel app = user : User user = app.action - |> Maybe.map .user + |> Maybe.andThen .user |> Maybe.withDefault defaultUser in { title = "Form Example" @@ -253,7 +253,7 @@ view maybeUrl sharedModel app = |> Html.text ] , app.action - |> Maybe.map .user + |> Maybe.andThen .user |> Maybe.map (\user_ -> Html.p @@ -274,6 +274,7 @@ view maybeUrl sharedModel app = , Attr.style "flex-direction" "column" , Attr.style "gap" "20px" ] + (app.action |> Maybe.andThen .formResponse) app defaultUser ] diff --git a/examples/pokedex/app/Route/Search.elm b/examples/pokedex/app/Route/Search.elm index 70f5be8a..74951fe3 100644 --- a/examples/pokedex/app/Route/Search.elm +++ b/examples/pokedex/app/Route/Search.elm @@ -196,7 +196,11 @@ view maybeUrl sharedModel model static = , form |> Form.toDynamicTransition "test1" |> Form.withGetMethod - |> Form.renderHtml [] static () + |> Form.renderHtml [] + -- TODO pass in server data + Nothing + static + () , static.data.results |> Maybe.map resultsView |> Maybe.withDefault (Html.div [] []) diff --git a/examples/pokedex/app/Route/Signup.elm b/examples/pokedex/app/Route/Signup.elm index eb7f1ae9..9f4eea2e 100644 --- a/examples/pokedex/app/Route/Signup.elm +++ b/examples/pokedex/app/Route/Signup.elm @@ -264,7 +264,11 @@ view maybeUrl sharedModel model static = , flashView static.data.flashMessage , form |> Form.toDynamicTransition "test1" - |> Form.renderHtml [] static () + |> Form.renderHtml [] + -- TODO pass in server data + Nothing + static + () ] } diff --git a/examples/pokedex/app/Route/Todos.elm b/examples/pokedex/app/Route/Todos.elm index d9c0eab3..9b21daf3 100644 --- a/examples/pokedex/app/Route/Todos.elm +++ b/examples/pokedex/app/Route/Todos.elm @@ -340,6 +340,8 @@ view maybeUrl sharedModel model static = [ Attr.style "display" "inline" , Attr.style "padding-left" "6px" ] + -- TODO pass in server data + Nothing static item.id ] @@ -360,6 +362,10 @@ view maybeUrl sharedModel model static = ) , createForm |> Form.toDynamicTransition "test2" - |> Form.renderHtml [] static () + |> Form.renderHtml [] + -- TODO pass in server data + Nothing + static + () ] } diff --git a/src/Form.elm b/src/Form.elm index d86a7c77..b3e8ce65 100644 --- a/src/Form.elm +++ b/src/Form.elm @@ -862,6 +862,11 @@ runOneOfServerSideWithServerValidations rawFormData parsers = {-| -} renderHtml : List (Html.Attribute (Pages.Msg.Msg msg)) + -> + Maybe + { fields : List ( String, String ) + , errors : Dict String (List error) + } -> AppContext app -> data -> @@ -873,8 +878,8 @@ renderHtml : -> List (Html (Pages.Msg.Msg msg)) ) -> Html (Pages.Msg.Msg msg) -renderHtml attrs app data (FinalForm options a b c) = - Html.Lazy.lazy5 renderHelper attrs options app data (Form a b c) +renderHtml attrs maybe app data (FinalForm options a b c) = + Html.Lazy.lazy6 renderHelper attrs maybe options app data (Form a b c) {-| -} @@ -968,104 +973,26 @@ renderStyledHtml attrs maybe app data (FinalForm options a b c) = renderHelper : List (Html.Attribute (Pages.Msg.Msg msg)) + -> + Maybe + { fields : List ( String, String ) + , errors : Dict String (List error) + } -> RenderOptions -> AppContext app -> data -> Form error (Validation error parsed named) data (Context error data -> List (Html (Pages.Msg.Msg msg))) -> Html (Pages.Msg.Msg msg) -renderHelper attrs options formState data (Form fieldDefinitions parser toInitialValues) = +renderHelper attrs maybe options formState data ((Form fieldDefinitions parser toInitialValues) as form) = -- TODO Get transition context from `app` so you can check if the current form is being submitted -- TODO either as a transition or a fetcher? Should be easy enough to check for the `id` on either of those? let - formId : String - formId = - options.name |> Maybe.withDefault "" + { formId, hiddenInputs, children } = + helperValues toHiddenInput maybe options formState data form - initialValues : Dict String Form.FieldState - initialValues = - toInitialValues data - |> List.map (Tuple.mapSecond (\value -> { value = value, status = Form.NotVisited })) - |> Dict.fromList - - part2 : Dict String Form.FieldState - part2 = - formState.pageFormState - |> Dict.get formId - |> Maybe.withDefault initFormState - |> .fields - - fullFormState : Dict String Form.FieldState - fullFormState = - initialValues - |> Dict.union part2 - - parsed : - { result : ( Validation error parsed named, Dict String (List error) ) - , view : Context error data -> List (Html (Pages.Msg.Msg msg)) - , serverValidations : DataSource (List ( String, List error )) - } - parsed = - parser (Just data) thisFormState - - merged : Validation error parsed named - merged = - mergeResults parsed - - thisFormState : Form.FormState - thisFormState = - formState.pageFormState - |> Dict.get formId - |> Maybe.withDefault Form.init - |> (\state -> { state | fields = fullFormState }) - - context : Context error data - context = - { errors = - merged - |> unwrapValidation - |> Tuple.second - |> Errors - , isTransitioning = - case formState.transition of - Just _ -> - -- TODO need to track the form's ID and check that to see if it's *this* - -- form that is submitting - --transition.todo == formId - True - - Nothing -> - False - , submitAttempted = thisFormState.submitAttempted - , data = data - } - - children = - parsed.view context - - hiddenInputs : List (Html (Pages.Msg.Msg msg)) - hiddenInputs = - fieldDefinitions - |> List.filterMap - (\( name, fieldDefinition ) -> - case fieldDefinition of - HiddenField -> - Just - (Html.input - [ Attr.name name - , Attr.type_ "hidden" - , Attr.value - (initialValues - |> Dict.get name - |> Maybe.map .value - |> Maybe.withDefault "" - ) - ] - [] - ) - - RegularField -> - Nothing - ) + toHiddenInput : List (Html.Attribute (Pages.Msg.Msg msg)) -> Html (Pages.Msg.Msg msg) + toHiddenInput hiddenAttrs = + Html.input hiddenAttrs [] in Html.form (Form.listeners formId @@ -1116,9 +1043,47 @@ renderStyledHelper : -> data -> Form error (Validation error parsed named) data (Context error data -> List (Html.Styled.Html (Pages.Msg.Msg msg))) -> Html.Styled.Html (Pages.Msg.Msg msg) -renderStyledHelper attrs maybe options formState data (Form fieldDefinitions parser toInitialValues) = +renderStyledHelper attrs maybe options formState data ((Form fieldDefinitions parser toInitialValues) as form) = -- TODO Get transition context from `app` so you can check if the current form is being submitted -- TODO either as a transition or a fetcher? Should be easy enough to check for the `id` on either of those? + let + { formId, hiddenInputs, children } = + helperValues toHiddenInput maybe options formState data form + + toHiddenInput : List (Html.Attribute (Pages.Msg.Msg msg)) -> Html.Styled.Html (Pages.Msg.Msg msg) + toHiddenInput hiddenAttrs = + Html.Styled.input (hiddenAttrs |> List.map StyledAttr.fromUnstyled) [] + in + Html.Styled.form + ((Form.listeners formId |> List.map StyledAttr.fromUnstyled) + ++ [ StyledAttr.method (methodToString options.method) + , StyledAttr.novalidate True + , case options.submitStrategy of + FetcherStrategy -> + StyledAttr.fromUnstyled <| Pages.Msg.fetcherOnSubmit formId (isValid parser data) + + TransitionStrategy -> + StyledAttr.fromUnstyled <| Pages.Msg.submitIfValid formId (isValid parser data) + ] + ++ attrs + ) + (hiddenInputs ++ children) + + +helperValues : + (List (Html.Attribute msg) -> view) + -> + Maybe + { fields : List ( String, String ) + , errors : Dict String (List error) + } + -> RenderOptions + -> AppContext app + -> data + ---> Form error parsed data view + -> Form error (Validation error parsed named) data (Context error data -> List view) + -> { formId : String, hiddenInputs : List view, children : List view } +helperValues toHiddenInput maybe options formState data (Form fieldDefinitions parser toInitialValues) = let formId : String formId = @@ -1149,10 +1114,6 @@ renderStyledHelper attrs maybe options formState data (Form fieldDefinitions par ) |> .fields - --formState.pageFormState - -- |> Dict.get formId - -- |> Maybe.withDefault initFormState - -- |> .fields fullFormState : Dict String Form.FieldState fullFormState = initialValues @@ -1160,7 +1121,7 @@ renderStyledHelper attrs maybe options formState data (Form fieldDefinitions par parsed : { result : ( Validation error parsed named, Dict String (List error) ) - , view : Context error data -> List (Html.Styled.Html (Pages.Msg.Msg msg)) + , view : Context error data -> List view , serverValidations : DataSource (List ( String, List error )) } parsed = @@ -1229,47 +1190,33 @@ renderStyledHelper attrs maybe options formState data (Form fieldDefinitions par children = parsed.view context - hiddenInputs : List (Html.Styled.Html (Pages.Msg.Msg msg)) + hiddenInputs : List view hiddenInputs = fieldDefinitions |> List.filterMap (\( name, fieldDefinition ) -> case fieldDefinition of HiddenField -> - Just - (Html.Styled.input - ([ Attr.name name - , Attr.type_ "hidden" - , Attr.value - (initialValues - |> Dict.get name - |> Maybe.map .value - |> Maybe.withDefault "" - ) - ] - |> List.map StyledAttr.fromUnstyled - ) - [] + [ Attr.name name + , Attr.type_ "hidden" + , Attr.value + (initialValues + |> Dict.get name + |> Maybe.map .value + |> Maybe.withDefault "" ) + ] + |> toHiddenInput + |> Just RegularField -> Nothing ) in - Html.Styled.form - ((Form.listeners formId |> List.map StyledAttr.fromUnstyled) - ++ [ StyledAttr.method (methodToString options.method) - , StyledAttr.novalidate True - , case options.submitStrategy of - FetcherStrategy -> - StyledAttr.fromUnstyled <| Pages.Msg.fetcherOnSubmit formId (isValid parser data) - - TransitionStrategy -> - StyledAttr.fromUnstyled <| Pages.Msg.submitIfValid formId (isValid parser data) - ] - ++ attrs - ) - (hiddenInputs ++ children) + { formId = formId + , hiddenInputs = hiddenInputs + , children = children + } {-| -}