elm-spa/docs/public/content/guide/03-pages.md
2021-04-26 00:55:48 -05:00

7.8 KiB
Raw Blame History

Pages

In elm-spa, every URL connects to a single page. Let's take a closer look at the homepage created with the elm-spa new command:

module Pages.Home_ exposing (view)

import Html
import View exposing (View)

view : View msg
view =
    { title = "Homepage"
    , body = [ Html.text "Hello, world!" ]
    }

This homepage renders "Homepage" in the browser tab, and "Hello, world!" onto the page.

Because the file is named Home_.elm, elm-spa knows this is the homepage. Visiting http://localhost:1234 in a web browser will render this view.

A view function is perfect when all you need is to render some HTML on the screen. But many web pages in the real world do more interesting things!

Upgrading "Hello, world!"

Let's start by adding a new page function:

module Pages.Home_ exposing (page)

import Html
import Page exposing (Page)
import Request exposing (Request)
import Shared
import View exposing (View)

page : Shared.Model -> Request -> Page
page shared req =
    Page.static
        { view = view
        }

view : View msg
view =
    { title = "Homepage"
    , body = [ Html.text "Hello, world!" ]
    }

We haven't changed the original code much- except we've added a new page function that:

  1. Accepts 2 inputs: Shared.Model and Request
  2. Returns a Page value
  3. Has been exposed at the top of the file.

Exposing page from this module lets elm-spa know we want to use page instead of the plain view function from before.

The view function from before is now passed into page. In the web browser, we still see "Hello, world!". However, this page now has access to two new bits of information!

  1. Shared.Model is our global application state, which might contain the signed-in user, settings, or other things that should persist as we move from one page to another.
  2. Request is a record with access to the current route, query parameters, and any other information about the current URL.

You can rely on the fact that the page will always be passed the latest Shared.Model and Request value. If we want either of these values to be available in our view function, we pass them in like so:

page : Shared.Model -> Request -> Page
page shared req =
    Page.static
        { view = view req  -- passing in req here!
        }

Now our view function can read the current URL value:

view : Request -> View msg
view req =
    { title = "Homepage"
    , body =
        [ Html.text ("Hello, " ++ req.url.host ++ "!")
        ]
    }

Now the browser should display "Hello, localhost!"

Beyond static pages

You might have noticed Page.static earlier in our page function. This is one of the built in page types that is built-in to elm-spa.

The rest of this section will introduce you to the other page types exposed by the Page module, so you know which one to reach for.

Always choose the simplest page type for the job and reach for the more advanced ones when your page really needs the extra features!

  • Page.static - for pages that only render a view.
  • Page.sandbox - for pages that need to keep track of state.
  • Page.element - for pages that send HTTP requests or continually listen for events from the browser or user.
  • Page.advanced - for pages that need to sign in a user or work with other details that should persist between page navigation.

Page.static

elm-spa add /example static

This was the page type we looked at above. It is perfect for pages that render static HTML, but might need access to the Shared.Model or Request values.

module Pages.Example exposing (page)


page : Shared.Model -> Request -> Page
page shared req =
    Page.static
        { view = view
        }


view : View msg

Page.sandbox

elm-spa add /example sandbox

This is the first page type that introduces the Elm architecture, which uses Model to store the current page state and Msg to define what actions users can take on this page.

It's time to upgrade to Page.sandbox when you need to track state on the page. Here are a few examples of things you'd store in page state:

  • The current slide of a carousel
  • The selected tab section to view
  • The open / close state of a modal

All these examples require us to be able to initialize a Model, update it based on Msg values sent from the view.

If you are new to the Elm architecture, be sure to visit guide.elm-lang.org. We'll be using it for all the upcoming page types!

module Pages.Example exposing (Model, Msg, page)


page : Shared.Model -> Request -> Page.With Model Msg
page shared req =
    Page.sandbox
        { init = init
        , update = update
        , view = view
        }


init : Model
update : Msg -> Model -> Model
view : Model -> View Msg

Our page function now returns Page.With Model Msg instead of Page. This is because our page is now stateful.

( Inspired by Browser.sandbox )

Page.element

elm-spa add /example element

When you are ready to send HTTP requests or subscribe to events like keyboard presses, mouse move, or incoming data from JS upgrade to Page.element.

This is the same as Page.sandbox, but introduces Cmd Msg and Sub Msg to handle side effects.

module Pages.Example exposing (Model, Msg, page)


page : Shared.Model -> Request -> Page.With Model Msg
page shared req =
    Page.element
        { init = init
        , update = update
        , view = view
        , subscriptions = subscriptions
        }


init : ( Model, Cmd Msg )
update : Msg -> Model -> ( Model, Cmd Msg )
view : Model -> View Msg
subscriptions : Model -> Sub Msg

( Inspired by Browser.element )

Page.advanced

elm-spa add /example advanced

For many applications, Page.element is all you need to store a Model, handle Msg values, and work with side-effects.

Some Elm users prefer sending global updates directly from their pages, so we've included this Page.advanced page type.

Using a custom Effect module, users are able to send Cmd Msg value via Effect.fromCmd or Shared.Msg values with Effect.fromSharedMsg.

module Pages.Example exposing (Model, Msg, page)


page : Shared.Model -> Request -> Page.With Model Msg
page shared req =
    Page.advanced
        { init = init
        , update = update
        , view = view
        , subscriptions = subscriptions
        }


init : ( Model, Effect Msg )
update : Msg -> Model -> ( Model, Effect Msg )
view : Model -> View Msg
subscriptions : Model -> Sub Msg

This Effect Msg value also allows support for folks using elm-program-test, which requires users to define their own custom type on top of Cmd Msg. More about that in the testing guide

Page.protected

Each of those four page types also have a protected version. This means pages are guaranteed to receive a User or redirect if no user is signed in.

-- not protected
Page.sandbox                  
    { init : Model
    , update : Msg -> Model -> Model
    , view : Model -> View Msg
    }

-- protected
Page.protected.sandbox        
    { init : User -> Model
    , update : User -> Msg -> Model -> Model
    , view : User -> Model -> View Msg
    }

When you are ready for user authentication, you can learn more about using Page.protected in the authentication guide.


Next up: Requests