Add FieldView.valueButton helpers, and rerun loaders when query params are changed to ensure that GET form submissions reload data.

This commit is contained in:
Dillon Kearns 2023-02-18 12:31:32 -08:00
parent df0489fc38
commit ba875d789f
5 changed files with 244 additions and 6 deletions

View File

@ -0,0 +1,11 @@
context("dev server with base path", () => {
it("submits a form to receive ActionData", () => {
cy.visit("/get-form");
cy.contains("Page 2").click();
cy.contains("Current page: 2");
cy.contains("Page 1").click();
cy.contains("Current page: 1");
cy.contains("Page 2").click();
cy.contains("Current page: 2");
});
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,143 @@
module Route.GetForm exposing (ActionData, Data, Model, Msg, route)
import BackendTask exposing (BackendTask)
import ErrorPage exposing (ErrorPage)
import FatalError exposing (FatalError)
import Form
import Form.Field as Field
import Form.FieldView
import Form.Validation as Validation
import Head
import Html exposing (Html)
import Html.Attributes as Attr
import Html.Styled
import Pages.Msg
import Pages.PageUrl exposing (PageUrl)
import RouteBuilder exposing (StatelessRoute, StaticPayload)
import Server.Request as Request exposing (Parser)
import Server.Response
import Shared
import View exposing (View)
type alias Model =
{}
type alias Msg =
()
type alias RouteParams =
{}
type alias ActionData =
{}
type alias Filters =
{ page : Int
}
form : Form.HtmlForm String Filters Filters Msg
form =
Form.init
(\page ->
{ combine =
Validation.succeed Filters
|> Validation.andMap page
, view =
\formState ->
[ page
|> Form.FieldView.valueButton "1"
[]
[ Html.text "Page 1" ]
, page
|> Form.FieldView.valueButton "2"
[]
[ Html.text "Page 2" ]
]
}
)
|> Form.field "page"
(Field.int { invalid = \_ -> "" }
|> Field.map (Maybe.withDefault 1)
--|> Field.withInitialValue (.first >> Form.Value.string)
)
route : StatelessRoute RouteParams Data ActionData
route =
RouteBuilder.serverRender
{ head = head
, data = data
, action = action
}
|> RouteBuilder.buildNoState { view = view }
type alias Data =
{ filters : Filters
}
data : RouteParams -> Parser (BackendTask FatalError (Server.Response.Response Data ErrorPage))
data routeParams =
Request.formData (Form.initCombined identity form)
|> Request.map
(\( formResponse, formResult ) ->
case formResult of
Ok filters ->
Data filters
|> Server.Response.render
|> BackendTask.succeed
Err _ ->
Data { page = 1 }
|> Server.Response.render
|> BackendTask.succeed
)
action : RouteParams -> Parser (BackendTask FatalError (Server.Response.Response ActionData ErrorPage))
action routeParams =
Request.succeed
(Server.Response.render {}
|> BackendTask.succeed
)
head :
StaticPayload Data ActionData RouteParams
-> List Head.Tag
head static =
[]
view :
Maybe PageUrl
-> Shared.Model
-> StaticPayload Data ActionData RouteParams
-> View (Pages.Msg.Msg Msg)
view maybeUrl sharedModel app =
{ title = "GET Form Example"
, body =
[ form
|> Form.toDynamicTransition "user-form"
|> Form.withGetMethod
|> Form.renderHtml
[ Attr.style "display" "flex"
, Attr.style "flex-direction" "column"
, Attr.style "gap" "20px"
]
(\_ -> Nothing)
app
app.data.filters
, Html.h2 []
[ Html.text <| "Current page: " ++ String.fromInt app.data.filters.page
]
]
|> List.map Html.Styled.fromUnstyled
}

View File

@ -1,16 +1,16 @@
module Form.FieldView exposing module Form.FieldView exposing
( Input(..), InputType(..), Options(..), input, inputTypeToString, radio, toHtmlProperties, Hidden(..), select ( Input(..), InputType(..), Options(..), input, inputTypeToString, radio, toHtmlProperties, Hidden(..), select, valueButton
, radioStyled, inputStyled , radioStyled, inputStyled, valueButtonStyled
) )
{-| {-|
@docs Input, InputType, Options, input, inputTypeToString, radio, toHtmlProperties, Hidden, select @docs Input, InputType, Options, input, inputTypeToString, radio, toHtmlProperties, Hidden, select, valueButton
## Html.Styled Helpers ## Html.Styled Helpers
@docs radioStyled, inputStyled @docs radioStyled, inputStyled, valueButtonStyled
-} -}
@ -103,6 +103,90 @@ type Options a
= Options (String -> Maybe a) (List String) = Options (String -> Maybe a) (List String)
{-| Gives you a submit button that will submit the form with a specific value for the given Field.
-}
valueButton :
String
-> List (Html.Attribute msg)
-> List (Html msg)
-> Form.Validation.Field error parsed Input
-> Html msg
valueButton exactValue attrs children (Validation viewField fieldName _) =
let
justViewField : ViewField Input
justViewField =
expectViewField viewField
rawField : { name : String, value : Maybe String, kind : ( Input, List ( String, Encode.Value ) ) }
rawField =
{ name = fieldName |> Maybe.withDefault ""
, value = Just exactValue --justViewField.value
, kind = justViewField.kind
}
in
case rawField.kind of
( Input inputType, properties ) ->
Html.button
(attrs
++ toHtmlProperties properties
++ [ (case inputType of
Checkbox ->
Attr.checked ((rawField.value |> Maybe.withDefault "") == "on")
_ ->
Attr.value (rawField.value |> Maybe.withDefault "")
-- TODO is this an okay default?
)
, Attr.name rawField.name
, inputType |> inputTypeToString |> Attr.type_
]
)
children
{-| Gives you a submit button that will submit the form with a specific value for the given Field.
-}
valueButtonStyled :
String
-> List (Html.Styled.Attribute msg)
-> List (Html.Styled.Html msg)
-> Form.Validation.Field error parsed Input
-> Html.Styled.Html msg
valueButtonStyled exactValue attrs children (Validation viewField fieldName _) =
let
justViewField : ViewField Input
justViewField =
expectViewField viewField
rawField : { name : String, value : Maybe String, kind : ( Input, List ( String, Encode.Value ) ) }
rawField =
{ name = fieldName |> Maybe.withDefault ""
, value = Just exactValue
, kind = justViewField.kind
}
in
case rawField.kind of
( Input inputType, properties ) ->
Html.Styled.button
(attrs
++ (toHtmlProperties properties |> List.map StyledAttr.fromUnstyled)
++ ([ (case inputType of
Checkbox ->
Attr.checked ((rawField.value |> Maybe.withDefault "") == "on")
_ ->
Attr.value (rawField.value |> Maybe.withDefault "")
-- TODO is this an okay default?
)
, Attr.name rawField.name
, inputType |> inputTypeToString |> Attr.type_
]
|> List.map StyledAttr.fromUnstyled
)
)
children
{-| -} {-| -}
input : input :
List (Html.Attribute msg) List (Html.Attribute msg)

View File

@ -414,7 +414,7 @@ update config appMsg model =
model model
Nothing -> Nothing ->
if model.url.path == url.path then if model.url.path == url.path && model.url.query == url.query then
( { model ( { model
| -- update the URL in case query params or fragment changed | -- update the URL in case query params or fragment changed
url = url url = url