ryannhg/elm-spa/3.0.2

NOTE: the latest version is 6.0.0

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 b0af558f3851 of https://github.com/ryan-haskell/elm-spa (zip prefix ryannhg-elm-spa-f014ad4/, America/Los_Angeles times): the zip is byte-identical to the one package.elm-lang.org recorded, SHA-1 fc643a8cc21170f063d242d017163dea46bfbaf6.

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.2/

Spa.Page

Pick the simplest page for the job!

  1. static - a page without state

  2. sandbox - a page without side-effects

  3. element - a page with side-effects

  4. component - a page that can change the global state

heads up: 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.

static

{ title : { global : globalModel } -> String
, view : PageContext route globalModel -> ui_pageMsg
}
-> Page
route
pageParams
()
Never
ui_pageMsg
layoutModel
layoutMsg
ui_layoutMsg
globalModel
globalMsg
msg
ui_msg

an example

page =
    Page.static
        { title = always title
        , view = always view
        }


title : String
title =
    "Example"


view : Html Never
view =
    h1 [ class "title" ] [ text "Example" ]

sandbox

{ title :
{ global : globalModel
, model : pageModel
}
-> String
, init : PageContext route globalModel -> pageParams -> pageModel
, update :
PageContext route globalModel -> pageMsg -> pageModel -> pageModel
, view : PageContext route globalModel -> pageModel -> ui_pageMsg
}
-> Page
route
pageParams
pageModel
pageMsg
ui_pageMsg
layoutModel
layoutMsg
ui_layoutMsg
globalModel
globalMsg
msg
ui_msg

an example

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 "-" ]
        ]

element

{ title :
{ global : globalModel
, model : pageModel
}
-> String
, init :
PageContext route globalModel
-> pageParams
-> ( pageModel, Cmd pageMsg )
, update :
PageContext route globalModel
-> pageMsg
-> pageModel
-> ( pageModel, Cmd pageMsg )
, view : PageContext route globalModel -> pageModel -> ui_pageMsg
, subscriptions :
PageContext route globalModel -> pageModel -> Sub pageMsg
}
-> Page
route
pageParams
pageModel
pageMsg
ui_pageMsg
layoutModel
layoutMsg
ui_layoutMsg
globalModel
globalMsg
msg
ui_msg

an example

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 =
    -- ...

component

{ title :
{ global : globalModel
, model : pageModel
}
-> String
, init :
PageContext route globalModel
-> pageParams
-> ( pageModel, Cmd pageMsg, Cmd globalMsg )
, update :
PageContext route globalModel
-> pageMsg
-> pageModel
-> ( pageModel, Cmd pageMsg, Cmd globalMsg )
, view : PageContext route globalModel -> pageModel -> ui_pageMsg
, subscriptions :
PageContext route globalModel -> pageModel -> Sub pageMsg
}
-> Page
route
pageParams
pageModel
pageMsg
ui_pageMsg
layoutModel
layoutMsg
ui_layoutMsg
globalModel
globalMsg
msg
ui_msg

an example

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 model
send : msg -> Cmd msg

A 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)
    )

manually composing pages?

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!

layout

((pageMsg -> msg) -> ui_pageMsg -> ui_msg)
-> Layout
route
pageParams
pageModel
pageMsg
ui_pageMsg
globalModel
globalMsg
msg
ui_msg
-> Page
route
pageParams
pageModel
pageMsg
ui_pageMsg
layoutModel
layoutMsg
ui_layoutMsg
globalModel
globalMsg
msg
ui_msg

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
            }
        }

recipe

((pageMsg -> layoutMsg) -> ui_pageMsg -> ui_layoutMsg)
-> Upgrade
route
pageParams
pageModel
pageMsg
ui_pageMsg
layoutModel
layoutMsg
ui_layoutMsg
globalModel
globalMsg
msg
ui_msg
-> Recipe
route
pageParams
pageModel
pageMsg
layoutModel
layoutMsg
ui_layoutMsg
globalModel
globalMsg
msg
ui_msg

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
                }
    
    
        -- ...
        }

wait... what's a "bundle"?

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 model

You 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 model

update helpers

layoutModel
-> Update route layoutModel layoutMsg globalModel globalMsg

If 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