orus-io/elm-spa/3.0.0

NOTE: the latest version is 3.0.1

Spa

A typical SPA application is defined in a few simple steps:

  • boostrap the application with init or initNoShared

  • add pages with addPublicPage and addProtectedPage

  • finalize the application with application (with the possible help of mapSharedMsg

    main =
        Spa.init
            { defaultView = View.defaultView
            , extractIdentity = Shared.identity
            }
            |> Spa.addPublicPage mappers Route.matchHome Home.page
            |> Spa.addPublicPage mappers Route.matchSignIn SignIn.page
            |> Spa.addProtectedPage mappers Route.matchCounter Counter.page
            |> Spa.addPublicPage mappers Route.matchTime Time.page
            |> Spa.application View.map
                { toRoute = Route.toRoute
                , init = Shared.init
                , update = Shared.update
                , subscriptions = Shared.subscriptions
                , toDocument = toDocument
                , protectPage = Route.toUrl >> Just >> Route.SignIn >> Route.toUrl
                }
            |> Browser.application

Create the application

{ defaultView : view
, extractIdentity : shared -> Maybe identity
}
-> Builder route identity shared sharedMsg view () () () ()

Bootstrap a Spa application

Spa.init
    { defaultView = View.defaultView
    , extractIdentity = Shared.identity
    }
  • defaultView is the default view that will be used when no other pages can be viewed. This should never happen once your app is properly setup (more on that a little further).

  • extractIdentity is a function that returns a Maybe identity from a Shared record. The actual identity type can be anything you want.

initNoShared : { defaultView : view } -> Builder route () () () view () () () ()

Bootstrap a Spa application that has no Shared state

Add pages

( CurrentViewMap
route
currentPageMsg
previousStackMsg
pageView
view
, PreviousViewMap
route
currentPageMsg
previousStackMsg
previousView
view
)
-> (route -> Maybe pageFlags)
-> ( shared
-> Page
pageFlags
sharedMsg
pageView
currentPageModel
currentPageMsg
)
route
identity
shared
sharedMsg
previousView
previousCurrent
previousPrevious
previousStackCurrentMsg
previousStackPreviousMsg
route
identity
shared
sharedMsg
view
currentPageModel
(Model SetupError previousCurrent previousPrevious)
currentPageMsg
(Msg route previousStackCurrentMsg previousStackPreviousMsg)

Add a public page to the application

|> Spa.addPublicPage (View.map, View.map) matchHome Pages.Home.page
  • mappers is a Tuple of view mappers. For example, if the application view is a Html msg, the mappers will be: ( Html.map, Html.map ). The duplication is for technical reasons (see the addPage function implementation).

  • match is a function that takes a route and returns the page flags if and only if the route matches the page. This is the place where data can be extracted from the route to be given to the page init function.

    A simple match function can be:

    matchHome : Route -> Maybe ()
    matchHome route =
        case route of
            Home ->
                Just ()
    
    
            _ ->
                Nothing

    A match function that extracts data from the route:

    matchSignIn : Route -> Maybe (Maybe String)
    matchSignIn route =
        case route of
            SignIn redirect ->
                Just redirect
    
    
            _ ->
                Nothing
  • page is a page constructor. A public page constructor is a function that takes the shared state:

    page : shared -> Page
( CurrentViewMap
route
currentPageMsg
previousStackMsg
pageView
view
, PreviousViewMap
route
currentPageMsg
previousStackMsg
previousView
view
)
-> (route -> Maybe pageFlags)
-> ( shared
-> identity
-> Page
pageFlags
sharedMsg
pageView
currentPageModel
currentPageMsg
)
route
identity
shared
sharedMsg
previousView
previousCurrent
previousPrevious
previousStackCurrentMsg
previousStackPreviousMsg
route
identity
shared
sharedMsg
view
currentPageModel
(Model SetupError previousCurrent previousPrevious)
currentPageMsg
(Msg route previousStackCurrentMsg previousStackPreviousMsg)

Add a protected page to the application

|> Spa.addProtectedPage (View.map, View.map) matchProfile Pages.Profile.page

The parameters are the same as addPublicPage, except that the page constructor takes the current identity in addition to the shared state:

page : shared -> identity -> Page

Finalize

Once all the pages are added to the application, we can change it into a record suitable for the Browser.application function.

(route -> sharedMsg)
route
identity
shared
sharedMsg
pageView
current
previous
currentMsg
previousMsg
route
identity
shared
sharedMsg
pageView
current
previous
currentMsg
previousMsg

Set a message for reacting to a route change, before the page is (re-)initialized

The shared state after handling this message will be passed to the page, but the effect will be combined to the page init effects.

( ( Msg route currentMsg previousMsg
-> Msg sharedMsg (Msg route currentMsg previousMsg)
)
-> pageView
-> view
)
-> { toRoute : Url -> route
, init : flags -> Key -> ( shared, Cmd sharedMsg )
, subscriptions : shared -> Sub sharedMsg
, update : sharedMsg -> shared -> ( shared, Cmd sharedMsg )
, protectPage : route -> String
, toDocument :
shared
-> view
-> Document (Msg sharedMsg (Msg route currentMsg previousMsg))
}
route
identity
shared
sharedMsg
pageView
current
previous
currentMsg
previousMsg
-> Application
flags
shared
sharedMsg
route
current
previous
currentMsg
previousMsg

Finalize the Spa application into a record suitable for the Browser.application

appWithPages
    |> Spa.application View.map
        { toRoute = Route.toRoute
        , protectPage = Route.toUrl >> Just >> Route.SignIn >> Route.toUrl
        , init = Shared.init
        , update = Shared.update
        , subscriptions = Shared.subscriptions
        , toDocument = toDocument
        }
    |> Browser.application

It takes a view mapper, then:

  • toRoute changes a Url.Url into a (custom) route
  • protectPage produces a redirection url when a protected route is accessed without being identified
  • init is the init function of the shared module
  • update is the update function of the shared module
  • subscriptions is the subscriptions function of the shared module
  • toDocument is a function that convert a view to a Browser.Document
mapSharedMsg : sharedMsg -> Msg sharedMsg pageMsg

maps a sharedMsg into a Msg. Useful in the 'toDocument' function, to add global actions that trigger shared messages

(UrlRequest -> sharedMsg)
-> Application
flags
shared
sharedMsg
route
current
previous
currentMsg
previousMsg
-> Application
flags
shared
sharedMsg
route
current
previous
currentMsg
previousMsg

Set a custom message for handling the onUrlRequest event of the Browser application.

The default handler does what most people expect (Nav.push internal urls, and Nav.load external urls).

Types

type Builder route identity shared sharedMsg view current previous currentMsg previousMsg

The intermediate type for building an application.

type alias Model route shared current previous =
{ key : Key
, currentRoute : route
, shared : shared
, page : Model SetupError current previous
}

The Application Model

type Msg sharedMsg pageMsg

The Application Msg type

A custom setup error for the underlying PageStack.Stack

type alias Application flags shared sharedMsg route current previous currentMsg previousMsg =
{ init :
flags
-> Url
-> Key
-> ( Model route shared current previous
, Cmd (Msg sharedMsg (Msg route currentMsg previousMsg))
)
, view :
Model route shared current previous
-> Document (Msg sharedMsg (Msg route currentMsg previousMsg))
, update :
Msg sharedMsg (Msg route currentMsg previousMsg)
-> Model route shared current previous
-> ( Model route shared current previous
, Cmd (Msg sharedMsg (Msg route currentMsg previousMsg))
)
, subscriptions :
Model route shared current previous
-> Sub (Msg sharedMsg (Msg route currentMsg previousMsg))
, onUrlRequest :
UrlRequest -> Msg sharedMsg (Msg route currentMsg previousMsg)
, onUrlChange :
Url -> Msg sharedMsg (Msg route currentMsg previousMsg)
}

The Application type that can be passed to Browser.application