Update todo example.

This commit is contained in:
Dillon Kearns 2023-05-16 10:55:54 -07:00
parent c45269f3a2
commit 05845559fd
5 changed files with 111 additions and 84 deletions

View File

@ -7,10 +7,10 @@ module Effect exposing (Effect(..), none, batch, fromCmd, map, perform)
-}
import Browser.Navigation
import Form.FormData exposing (FormData)
import Http
import Json.Decode as Decode
import Pages.Fetcher
import Pages.ProgramConfig exposing (FormData)
import Url exposing (Url)

View File

@ -11,7 +11,8 @@ import FatalError exposing (FatalError)
import Form
import Form.Field as Field
import Form.FieldView
import Form.Validation as Validation exposing (Combined, Field)
import Form.Handler
import Form.Validation as Validation exposing (Field, Validation)
import Head
import Head.Seo as Seo
import Html exposing (Html)
@ -20,6 +21,7 @@ import Json.Decode as Decode
import Json.Encode as Encode
import List.Nonempty
import MySession
import Pages.Form
import Pages.Script as Script
import Pages.Url
import PagesMsg exposing (PagesMsg)
@ -86,9 +88,9 @@ type alias EnvVariables =
}
form : Form.DoneForm String (BackendTask FatalError (Combined String EmailAddress)) data (List (Html (PagesMsg Msg))) Msg
form : Pages.Form.FormWithServerValidations String EmailAddress data (List (Html (PagesMsg Msg)))
form =
Form.init
Form.form
(\fieldEmail ->
{ combine =
Validation.succeed
@ -119,7 +121,7 @@ form =
[ fieldEmail |> fieldView info "Email"
, globalErrors info
, Html.button []
[ if info.isTransitioning then
[ if info.submitting then
Html.text "Logging in..."
else
@ -132,15 +134,15 @@ form =
|> Form.hiddenKind ( "kind", "login" ) "Expected kind"
logoutForm : Form.DoneForm String () data (List (Html (PagesMsg Msg))) Msg
logoutForm : Form.HtmlForm String () data msg
logoutForm =
Form.init
Form.form
{ combine =
Validation.succeed ()
, view =
\info ->
[ Html.button []
[ if info.isTransitioning then
[ if info.submitting then
Html.text "Logging out..."
else
@ -287,21 +289,21 @@ data routeParams =
)
allForms : Form.ServerForms String (BackendTask FatalError (Combined String Action))
allForms : Pages.Form.Handler String EmailAddress
allForms =
logoutForm
|> Form.toServerForm
|> Form.initCombinedServer (\_ -> Logout)
|> Form.combineServer LogIn form
Form.Handler.init identity form
--logoutForm
-- |> Form.Handler.init (\_ -> Logout)
-- |> Form.Handler.with LogIn form
action : RouteParams -> Request.Parser (BackendTask FatalError (Response ActionData ErrorPage))
action routeParams =
Request.map2 Tuple.pair
(Request.oneOf
[ Request.formDataWithServerValidation allForms
]
)
(Request.formDataWithServerValidation allForms)
Request.requestTime
|> MySession.withSession
(\( resolveFormBackendTask, requestTime ) session ->
@ -323,13 +325,13 @@ action routeParams =
)
|> BackendTask.succeed
Ok ( _, Logout ) ->
( Session.empty
, Route.redirectTo Route.Login
)
|> BackendTask.succeed
Ok ( _, LogIn emailAddress ) ->
--Ok ( _, Logout ) ->
-- ( Session.empty
-- , Route.redirectTo Route.Login
-- )
-- |> BackendTask.succeed
--
Ok ( _, emailAddress ) ->
( okSession
, { maybeError = Nothing
, sentLink = True
@ -372,7 +374,7 @@ type alias Data =
type alias ActionData =
{ maybeError : Maybe (Form.Response String)
{ maybeError : Maybe (Form.ServerResponse String)
, sentLink : Bool
}
@ -395,19 +397,22 @@ view app shared =
Html.div []
[ Html.text <| "Hello! You are already logged in as " ++ username
, logoutForm
|> Form.toDynamicTransition
|> Form.renderHtml "logout"
[]
(\_ -> Nothing)
|> Pages.Form.renderHtml []
Pages.Form.Parallel
(Form.options "logout")
app
()
]
Nothing ->
Html.text "You aren't logged in yet."
]
, form
|> Form.renderHtml "login" [] .maybeError app ()
|> Pages.Form.renderHtml []
Pages.Form.Serial
(Form.options "login"
|> Form.withServerResponse (app.action |> Maybe.andThen .maybeError)
)
app
]
]
}

View File

@ -20,6 +20,7 @@ import Json.Decode as Decode
import Json.Encode as Encode
import LoadingSpinner
import MySession
import Pages.Form
import Pages.Transition exposing (FetcherSubmitStatus(..))
import PagesMsg exposing (PagesMsg)
import Route
@ -290,12 +291,12 @@ action routeParams =
|> Result.withDefault Session.empty
in
case formResult of
Ok actionInput ->
Form.Valid actionInput ->
(okSession |> Session.get "sessionId" |> Maybe.withDefault "")
|> performAction requestTime actionInput
|> BackendTask.map (Tuple.pair okSession)
Err _ ->
Form.Invalid _ _ ->
BackendTask.succeed ( okSession, Response.render { errors = Nothing } )
)
@ -353,7 +354,8 @@ view app shared model =
_ ->
allForms
|> Form.Handler.run payload.fields
|> Tuple.first
|> Form.toResult
|> Result.toMaybe
)
creatingItems : List Entry
@ -472,11 +474,10 @@ view app shared model =
case
( allForms
|> Form.Handler.run payload.fields
|> Tuple.first
, status
)
of
( Just (Add newItem), Pages.Transition.FetcherComplete (Just parsedActionData) ) ->
( Form.Valid (Add newItem), Pages.Transition.FetcherComplete (Just parsedActionData) ) ->
parsedActionData.errors
|> Maybe.map (Tuple.pair key)
@ -492,30 +493,32 @@ view app shared model =
[ section
[ class "todoapp" ]
[ addItemForm
|> Form.toDynamicFetcher
|> Form.withOnSubmit (\_ -> NewItemSubmitted)
|> Form.renderHtml
("new-item-"
++ (model.nextId |> Time.posixToMillis |> String.fromInt)
)
|> Pages.Form.renderHtml
[ class "create-form"
, hidden (not (List.isEmpty failedAddItemActions))
]
(\_ -> Nothing)
Pages.Form.Parallel
(Form.options
("new-item-"
++ (model.nextId |> Time.posixToMillis |> String.fromInt)
)
|> Form.withInput Nothing
|> Form.withOnSubmit (\_ -> NewItemSubmitted)
)
app
Nothing
, div []
(failedAddItemActions
|> List.indexedMap
(\index ( key, createFetcherErrors ) ->
addItemForm
|> Form.toDynamicFetcher
|> Form.withOnSubmit (\_ -> NewItemSubmitted)
|> Form.renderHtml key
|> Pages.Form.renderHtml
[ class "create-form", hidden (index /= 0) ]
(\_ -> Nothing)
Pages.Form.Parallel
(Form.options key
|> Form.withOnSubmit (\_ -> NewItemSubmitted)
|> Form.withInput (Just createFetcherErrors)
)
app
(Just createFetcherErrors)
)
)
, viewEntries app optimisticVisibility optimisticEntities
@ -542,7 +545,7 @@ allForms =
|> Form.Handler.with CheckAll toggleAllForm
addItemForm : Form.HtmlForm String String (Maybe String) Msg
addItemForm : Form.HtmlForm String String (Maybe String) msg
addItemForm =
Form.form
(\description ->
@ -560,7 +563,7 @@ addItemForm =
, autofocus True
]
description
, formState.data |> Maybe.map (\error -> Html.div [ class "error", id "new-todo-error" ] [ text error ]) |> Maybe.withDefault (text "")
, formState.input |> Maybe.map (\error -> Html.div [ class "error", id "new-todo-error" ] [ text error ]) |> Maybe.withDefault (text "")
]
]
}
@ -569,7 +572,7 @@ addItemForm =
|> Form.hiddenKind ( "kind", "new-item" ) "Expected kind"
editItemForm : Form.HtmlForm String ( String, String ) Entry Msg
editItemForm : Form.HtmlForm String ( String, String ) Entry msg
editItemForm =
Form.form
(\itemId description ->
@ -582,7 +585,7 @@ editItemForm =
[ FieldView.input
[ class "edit-input"
, name "title"
, id ("todo-" ++ formState.data.id)
, id ("todo-" ++ formState.input.id)
]
description
]
@ -590,18 +593,18 @@ editItemForm =
)
|> Form.hiddenField "itemId"
(Field.text
|> Field.withInitialValue (.id >> Form.Value.string)
|> Field.withInitialValue .id
|> Field.required "Must be present"
)
|> Form.field "description"
(Field.text
|> Field.withInitialValue (.description >> Form.Value.string)
|> Field.withInitialValue .description
|> Field.required "Must be present"
)
|> Form.hiddenKind ( "kind", "edit-item" ) "Expected kind"
deleteItemForm : Form.HtmlForm String String Entry Msg
deleteItemForm : Form.HtmlForm String String Entry msg
deleteItemForm =
Form.form
(\todoId ->
@ -616,12 +619,12 @@ deleteItemForm =
|> Form.hiddenField "todoId"
(Field.text
|> Field.required "Must be present"
|> Field.withInitialValue (.id >> Form.Value.string)
|> Field.withInitialValue .id
)
|> Form.hiddenKind ( "kind", "delete" ) "Expected kind"
toggleAllForm : Form.HtmlForm String Bool { allCompleted : Bool } Msg
toggleAllForm : Form.HtmlForm String Bool { allCompleted : Bool } msg
toggleAllForm =
Form.form
(\toggleTo ->
@ -634,7 +637,7 @@ toggleAllForm =
[ classList
[ ( "toggle-all", True )
, ( "toggle", True )
, ( "checked", formState.data.allCompleted )
, ( "checked", formState.input.allCompleted )
]
]
[ text "" ]
@ -646,15 +649,12 @@ toggleAllForm =
)
|> Form.hiddenField "toggleTo"
(Field.checkbox
|> Field.withInitialValue
(\{ allCompleted } ->
Form.Value.bool (not allCompleted)
)
|> Field.withInitialValue (not << .allCompleted)
)
|> Form.hiddenKind ( "kind", "toggle-all" ) "Expected kind"
checkItemForm : Form.HtmlForm String ( Bool, String ) Entry Msg
checkItemForm : Form.HtmlForm String ( Bool, String ) Entry msg
checkItemForm =
Form.form
(\todoId complete ->
@ -665,7 +665,7 @@ checkItemForm =
, view =
\formState ->
[ Html.button [ class "toggle" ]
[ if formState.data.completed then
[ if formState.input.completed then
Icon.complete
else
@ -677,16 +677,16 @@ checkItemForm =
|> Form.hiddenField "todoId"
(Field.text
|> Field.required "Must be present"
|> Field.withInitialValue (.id >> Form.Value.string)
|> Field.withInitialValue .id
)
|> Form.hiddenField "complete"
(Field.checkbox
|> Field.withInitialValue (.completed >> not >> Form.Value.bool)
|> Field.withInitialValue (.completed >> not)
)
|> Form.hiddenKind ( "kind", "complete" ) "Expected kind"
clearCompletedForm : Form.HtmlForm String () { entriesCompleted : Int } Msg
clearCompletedForm : Form.HtmlForm String () { entriesCompleted : Int } msg
clearCompletedForm =
Form.form
{ combine = Validation.succeed ()
@ -694,9 +694,9 @@ clearCompletedForm =
\formState ->
[ button
[ class "clear-completed"
, hidden (formState.data.entriesCompleted == 0)
, hidden (formState.input.entriesCompleted == 0)
]
[ text ("Clear completed (" ++ String.fromInt formState.data.entriesCompleted ++ ")")
[ text ("Clear completed (" ++ String.fromInt formState.input.entriesCompleted ++ ")")
]
]
}
@ -736,8 +736,12 @@ viewEntries app visibility entries =
, style "visibility" cssVisibility
]
[ toggleAllForm
|> Form.toDynamicFetcher
|> Form.renderHtml "toggle-all" [] (\_ -> Nothing) app { allCompleted = allCompleted }
|> Pages.Form.renderHtml []
Pages.Form.Parallel
(Form.options "toggle-all"
|> Form.withInput { allCompleted = allCompleted }
)
app
, Keyed.ul [ class "todo-list" ] <|
List.map (viewKeyedEntry app) (List.filter isVisible entries)
]
@ -762,18 +766,31 @@ viewEntry app todo =
[ div
[ class "view" ]
[ checkItemForm
|> Form.toDynamicFetcher
|> Form.renderHtml ("toggle-" ++ todo.id) [] (\_ -> Nothing) app todo
|> Pages.Form.renderHtml []
Pages.Form.Parallel
(("toggle-" ++ todo.id)
|> Form.options
|> Form.withInput todo
)
app
, editItemForm
|> Form.toDynamicFetcher
|> Form.renderHtml ("edit-" ++ todo.id) [] (\_ -> Nothing) app todo
|> Pages.Form.renderHtml []
Pages.Form.Parallel
(Form.options ("edit-" ++ todo.id)
|> Form.withInput todo
)
app
, if todo.isSaving then
LoadingSpinner.view
else
deleteItemForm
|> Form.toDynamicFetcher
|> Form.renderHtml ("delete-" ++ todo.id) [] (\_ -> Nothing) app todo
|> Pages.Form.renderHtml []
Pages.Form.Parallel
(Form.options ("delete-" ++ todo.id)
|> Form.withInput todo
)
app
]
]
@ -863,8 +880,12 @@ visibilitySwap visibilityParam visibility actualVisibility =
viewControlsClear : App Data ActionData RouteParams -> Int -> Html (PagesMsg Msg)
viewControlsClear app entriesCompleted =
clearCompletedForm
|> Form.toDynamicFetcher
|> Form.renderHtml "clear-completed" [] (\_ -> Nothing) app { entriesCompleted = entriesCompleted }
|> Pages.Form.renderHtml []
Pages.Form.Parallel
(Form.options "clear-completed"
|> Form.withInput { entriesCompleted = entriesCompleted }
)
app
infoFooter : Html msg

View File

@ -26,7 +26,7 @@ withSession =
Session.withSessionResult
{ name = "mysession"
, secrets = secrets
, options = cookieOptions
, options = Just cookieOptions
}
@ -38,7 +38,7 @@ withSessionOrRedirect toRequest handler =
Session.withSessionResult
{ name = "mysession"
, secrets = secrets
, options = cookieOptions
, options = Just cookieOptions
}
(\request sessionResult ->
sessionResult
@ -68,7 +68,7 @@ expectSessionOrRedirect toRequest handler =
Session.withSessionResult
{ name = "mysession"
, secrets = secrets
, options = cookieOptions
, options = Just cookieOptions
}
(\request sessionResult ->
sessionResult

View File

@ -98,6 +98,7 @@ import Internal.Request
import Json.Decode
import Json.Encode
import List.NonEmpty
import Pages.Form
import QueryParams
import Time
import Url
@ -891,7 +892,7 @@ runForm validation =
{-| -}
formDataWithServerValidation :
Form.Handler.Handler error (BackendTask FatalError (Validation.Validation error combined kind constraints))
Pages.Form.Handler error combined
-> Parser (BackendTask FatalError (Result (Form.ServerResponse error) ( Form.ServerResponse error, combined )))
formDataWithServerValidation formParsers =
rawFormData