Renamed to ryan-haskell/elm-spa. It is published under that name, and new versions come out there.
Versions published as ryannhg/elm-spa still install under that name. 13 versions of 13 come from the new location (see their notes).
Rerouted (renamed, verified). The GitHub user or repository was renamed. The tag is still there, at the new name. The Lamdera package server serves a copy recovered from https://github.com/ryan-haskell/elm-spa.
What we checked: The repo moved to github.com/ryan-haskell/elm-spa. Rebuilt with git archive from commit d4517fdd0d21 of https://github.com/ryan-haskell/elm-spa (zip prefix ryannhg-elm-spa-a1cd9c1/, America/Los_Angeles times): the zip is byte-identical to the one package.elm-lang.org recorded, SHA-1 7f9d9d9ae8c43b76a789a680d27edbcda446bb62.
The Lamdera compiler uses this copy and prints a one-line notice saying so. elm itself fails to download this version.
Details · package.elm-lang.org's zip: https://github.com/ryannhg/elm-spa/zipball/3.0.1/
static - a page without state
sandbox - a page without side-effects
element - a page with side-effects
component - a page that can change the global state
always incoming!You may notice the examples below use the function always.
Page.static
{ title = always "Hello"
, view = always view
}This is to opt-out each function from accessing data like PageContext
If you decide you need access to the Route, query parameters, or Global.Model:
Remove the always from title, init, update, view, or
subscriptions functions.
page =
Page.sandbox
{ title = always title
, init = always init
, update = always update
, view = always view
}
title : String
title =
"Counter"
type alias Model =
Int
init : Model
init =
0
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
view : Model -> Html Msg
view model =
div []
[ button [ Events.onClick Increment ] [ text "+" ]
, text (String.fromInt model)
, button [ Events.onClick Decrement ] [ text "-" ]
]page =
Page.element
{ title = always title
, init = always init
, update = always update
, subscriptions = always subscriptions
, view = always view
}
title : String
title =
"Cat Gifs"
init : ( Model, Cmd.none )
init =
-- ...
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
-- ...
subscriptions : Model -> Sub Msg
subscriptions model =
-- ...
view : Model -> Html Msg
view model =
-- ...page =
Page.component
{ title = always title
, init = always init
, update = always update
, subscriptions = always subscriptions
, view = view -- no always used here, so view
-- has access to `PageContext`
}
title : String
title =
"Sign in"
init : Params.SignIn -> ( Model, Cmd Msg, Cmd Global.Msg )
init params =
-- ...
update : Msg -> Model -> ( Model, Cmd Msg, Cmd Global.Msg )
update msg model =
-- ...
subscriptions : Model -> Sub Msg
subscriptions model =
-- ...
view : Spa.PageContext -> Model -> Html Msg
view { global } model =
case global.user of
SignedIn user ->
viewSignOutForm user model
SignedOut ->
viewSignInForm modelA utility for sending Global.Msg commands from your Page.component
init : Params.SignIn -> ( Model, Cmd Msg, Cmd Global.Msg )
init params =
( model
, Cmd.none
, Page.send (Global.NavigateTo routes.dashboard)
)The rest of this module contains types and functions that are automatically generated with the CLI companion tool!
If you'd rather type this stuff manually, these docs are for you!
In practice, we wrap layout in Utils/Spa.elm so we only have to provide Html.map or Element.map once)
import Utils.Spa as Spa
page =
Spa.layout
{ layout = Layout.view
, pages =
{ init = init
, update = update
, bundle = bundle
}
}Implementing the init, update and bundle functions is much easier
when you turn a Page type into a Recipe.
A Recipe is just an Elm record waiting for its page specific data.
init: just needs a route
upgrade : just needs a msg and model
bundle (view/subscriptions) : just needs a model
import Utils.Spa as Spa
recipes : Recipes msg
recipes =
{ top =
Spa.recipe
{ page = Top.page
, toModel = TopModel
, toMsg = TopMsg
}
, counter =
Spa.recipe
{ page = Counter.page
, toModel = CounterModel
, toMsg = CounterMsg
}
-- ...
}We can "bundle" the title,view, and subscriptions functions together,
because they only need access to the current model.
So instead of typing out all this:
title bigModel =
case bigModel of
FooModel model ->
foo.title model
BarModel model ->
bar.title model
BazModel model ->
baz.title model
view bigModel =
case bigModel of
FooModel model ->
foo.view model
BarModel model ->
bar.view model
BazModel model ->
baz.view model
subscriptions bigModel =
case bigModel of
FooModel model ->
foo.subscriptions model
BarModel model ->
bar.subscriptions model
BazModel model ->
baz.subscriptions modelYou only create one case expression: (woohoo, less typing!)
bundle bigModel =
case bigModel of
FooModel model ->
foo.bundle model
BarModel model ->
bar.bundle model
BazModel model ->
baz.bundle modelIf the update function receives a msg that doesn't
match up its model, we use keep to leave the page as-is.
update : Msg -> Model -> Spa.Update Model Msg
update bigMsg bigModel =
case ( bigMsg, bigModel ) of
( TopMsg msg, TopModel model ) ->
top.update msg model
( CounterMsg msg, CounterModel model ) ->
counter.update msg model
( NotFoundMsg msg, NotFoundModel model ) ->
notFound.update msg model
_ ->
Page.keep bigModel
an example