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
init : { 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
addPublicPage : ( PageStack.CurrentViewMap route currentPageMsg previousStackMsg pageView view, PageStack.PreviousViewMap route currentPageMsg previousStackMsg previousView view ) -> (route -> Maybe pageFlags) -> (shared -> Page pageFlags sharedMsg pageView currentPageModel currentPageMsg) -> Builder route identity shared sharedMsg previousView previousCurrent previousPrevious previousStackCurrentMsg previousStackPreviousMsg -> Builder route identity shared sharedMsg view currentPageModel (PageStack.Model SetupError previousCurrent previousPrevious) currentPageMsg (PageStack.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
addProtectedPage : ( PageStack.CurrentViewMap route currentPageMsg previousStackMsg pageView view, PageStack.PreviousViewMap route currentPageMsg previousStackMsg previousView view ) -> (route -> Maybe pageFlags) -> (shared -> identity -> Page pageFlags sharedMsg pageView currentPageModel currentPageMsg) -> Builder route identity shared sharedMsg previousView previousCurrent previousPrevious previousStackCurrentMsg previousStackPreviousMsg -> Builder route identity shared sharedMsg view currentPageModel (PageStack.Model SetupError previousCurrent previousPrevious) currentPageMsg (PageStack.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
Once all the pages are added to the application, we can change it into a record
suitable for the Browser.application
function.
beforeRouteChange : (route -> sharedMsg) -> Builder route identity shared sharedMsg pageView current previous currentMsg previousMsg -> Builder 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.
application : ((PageStack.Msg route currentMsg previousMsg -> Msg sharedMsg (PageStack.Msg route currentMsg previousMsg)) -> pageView -> view) -> { toRoute : Url -> route, init : flags -> Browser.Navigation.Key -> ( shared, Platform.Cmd.Cmd sharedMsg ), subscriptions : shared -> Platform.Sub.Sub sharedMsg, update : sharedMsg -> shared -> ( shared, Platform.Cmd.Cmd sharedMsg ), protectPage : route -> String, toDocument : shared -> view -> Browser.Document (Msg sharedMsg (PageStack.Msg route currentMsg previousMsg)) } -> Builder 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) routeprotectPage
produces a redirection url when a protected route is accessed
without being identifiedinit
is the init function of the shared moduleupdate
is the update function of the shared modulesubscriptions
is the subscriptions function of the shared moduletoDocument
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
onUrlRequest : (Browser.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).
The intermediate type for building an application.
{ key : Browser.Navigation.Key
, currentRoute : route
, shared : shared
, page : PageStack.Model SetupError current previous
}
The Application Model
The Application Msg type
A custom setup error for the underlying PageStack.Stack
{ init : flags -> Url -> Browser.Navigation.Key -> ( Model route shared current previous
, Platform.Cmd.Cmd (Msg sharedMsg (PageStack.Msg route currentMsg previousMsg)) )
, view : Model route shared current previous -> Browser.Document (Msg sharedMsg (PageStack.Msg route currentMsg previousMsg))
, update : Msg sharedMsg (PageStack.Msg route currentMsg previousMsg) -> Model route shared current previous -> ( Model route shared current previous
, Platform.Cmd.Cmd (Msg sharedMsg (PageStack.Msg route currentMsg previousMsg)) )
, subscriptions : Model route shared current previous -> Platform.Sub.Sub (Msg sharedMsg (PageStack.Msg route currentMsg previousMsg))
, onUrlRequest : Browser.UrlRequest -> Msg sharedMsg (PageStack.Msg route currentMsg previousMsg)
, onUrlChange : Url -> Msg sharedMsg (PageStack.Msg route currentMsg previousMsg)
}
The Application type that can be passed to Browser.application