orus-io / elm-spa / Spa

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

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

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

Bootstrap a Spa application that has no Shared state

Add pages

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

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

Finalize

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:

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

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 : Browser.Navigation.Key
, currentRoute : route
, shared : shared
, page : PageStack.Model SetupError current previous 
}

The Application Model


type Msg sharedMsg pageMsg

The Application Msg type


type SetupError

A custom setup error for the underlying PageStack.Stack


type alias Application flags shared sharedMsg route current previous currentMsg previousMsg =
{ 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