This module allows you to combine multiple apps created with Browser.element or Browser.sandbox into a single application, with or without URL control. The key feature of the Program type is that, unlike other attempts to simplify SPA development, it does not use type variables for inner model and messages (it's Program flag, not Program flag model msg). This allows you to combine all the pages into a single List, Dict, Random.Generator, or any other data structure you like.
Users need to write a bit of boilerplate. For example, if you want to use the digital clock application (discussed in the official guide's subscription section), you'll need to set up a corresponding module like this. (This code is based on the one in the official guide, with first line added and Page substituted for Browser and program for main.)
module DigitalClock exposing (program)
-- Show the current time in your time zone.
--
-- Read how it works:
-- https://guide.elm-lang.org/effects/time.html
--
-- For an analog clock, check out this SVG example:
-- https://elm-lang.org/examples/clock
--
import Html exposing (..)
import Page
import Task
import Time
-- MAIN
program =
Page.element
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Model =
{ zone : Time.Zone
, time : Time.Posix
}
init : () -> ( Model, Cmd Msg )
init _ =
( Model Time.utc (Time.millisToPosix 0)
, Task.perform AdjustTimeZone Time.here
)
-- UPDATE
type Msg
= Tick Time.Posix
| AdjustTimeZone Time.Zone
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick newTime ->
( { model | time = newTime }
, Cmd.none
)
AdjustTimeZone newZone ->
( { model | zone = newZone }
, Cmd.none
)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every 1000 Tick
-- VIEW
view : Model -> Html Msg
view model =
let
hour =
String.fromInt (Time.toHour model.zone model.time)
minute =
String.fromInt (Time.toMinute model.zone model.time)
second =
String.fromInt (Time.toSecond model.zone model.time)
in
h1 [] [ text (hour ++ ":" ++ minute ++ ":" ++ second) ]You can use this page module as follows:
module Main exposing (..)
import Browser
import DigitalClock
import Html exposing (..)
import Page
main : Program () Model Msg
main =
Browser.element
{ init = init
, subscriptions = subscriptions
, update = update
, view = view
}
type alias Model =
{ page : Page.Model
}
init : () -> ( Model, Cmd Msg )
init _ =
update
(Page.mapInit PageInitialized DigitalClock.program ())
{ page = Page.empty }
subscriptions : Model -> Sub Msg
subscriptions model =
Page.mapSubscriptions GotPageMsg model.page
type Msg
= PageInitialized ( Page.Model, Cmd Page.Msg )
| GotPageMsg Page.Msg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
PageInitialized ( initialPage, initialCmd ) ->
( { model
| page = initialPage
}
, Cmd.map GotPageMsg initialCmd
)
GotPageMsg pMsg ->
let
( newPage, newPageCmd ) =
Page.update pMsg model.page
in
( { model
| page = newPage
}
, Cmd.map GotPageMsg newPageCmd
)
view : Model -> Html Msg
view model =
Page.mapView GotPageMsg model.pageFor usage with multiple pages, see the GitHub repository.
A custom type representing Msgs in your page module. You need to use Page.update to access this value. See the example above.
Page.Msg is distinct from the Msg defined in individual page modules.Page.Msg provides a level of abstraction that enables consistent handling of all your pages.Pages are only possible with flags and ports. If your application needs more communication between pages, please take a look at Emitter module as well.A custom type representing the main program of a page. Values of this type can be passed to the Page.mapInit function with a flag.
Functions similarly to Browser.element. The return value is wrapped in Page.Program flag and can be passed to Page.mapInit along with a flag.
Functions similarly to Browser.sandbox. You can create a Page.Program () easily by just using Page.sandbox instead of Browser.sandbox.
The only way you can start new page.
The only way to handle Page.Msg value from a child component.
Page.Msg value as the first argument and the Page.Model value as the second argument belongs to the same page.mapInit function call. (check the Process.sleep example in the gitHub repository.)You can use your page with this function. See the example above.
Don't forget to use this in your subscriptions in the parent module!
A custom type representing internal states of your page.
Page.Modelis distinct from theModeldefined in individual page modules.Page.Modelprovides a level of abstraction that enables consistent handling of all your pages.Modelin each page module cannot be accessed from outside of that page.