This module allows you to combine multiple apps created with Browser.element into a single application, with or without URL control. The key feature of the Page type is that, unlike other attempts to simplify SPA development, it does not use type variables (it's Page, not Page model msg). This allows you to combine all the pages into a single List Page, Dict String Page, Random.Generator Page, or any other data structure.
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 Page substituted for Browser and page for main.)
module Pages.Clock exposing (page)
-- 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
initialize =
Page.element
-- -main = Browser.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) ]And the main app looks like this:
module Main exposing (..)
import Browser
import Html as H exposing (Html)
import Html.Events exposing (onClick)
import Pages.Clock
type alias Model =
{ page : Page
}
type Msg
= PageUpdated Page.Msg
| PageReset
init : () -> ( Model, Cmd Msg )
init _ =
let
( initialPage, initialCmd ) =
Page.unwrapMsg <| Pages.Clock.initiate ()
in
( Model initialPage
, Cmd.map PageUpdated initialCmd
)
subscriptions : Model -> Sub Msg
subscriptions model =
-- Don't forget to add this line!
Page.subscribe model.page
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
PageUpdated m ->
-- These lines are boilerplate code.
-- You can copy & paste them.
let
( newPage, newCmd ) =
Page.unwrapMsg m
in
( { model
| page = newPage
}
, Cmd.map PageUpdated newCmd
)
PageReset ->
init ()
view : Model -> Html Msg
view model =
H.div []
[ Page.mapView PageUpdated model.page
, H.br [] []
, H.button [ onClick PageReset ] [ H.text "Reset" ]
]
main : Program () Model Msg
main =
Browser.element
{ init = init
, subscriptions = subscriptions
, update = update
, view = view
}For usage with multiple pages, see the GitHub repository.
A Msg type emitted from the page module. You need to use unwrapMsg to access this value. See the example above.
Similar to Browser.sandbox. You can create a Page easily by just substituting Page for Browser.
Similar to Browser.element. At initialization, you can pass a value of any type as flags. The resultant (Page, Cmd Msg) will be returned being wrapped with Page.Msg, so you need to prepare a receiver message in the parent module like the example above.
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 that can store the same information as
Browser.element.