elm-spa/README.md

857 lines
19 KiB
Markdown
Raw Normal View History

2019-10-05 01:16:02 +03:00
# ryannhg/elm-app
> a way to build single page apps with Elm.
2019-10-08 08:22:17 +03:00
## installing
```
elm install ryannhg/elm-app
```
## motivation
Every time I try and create a single page application from scratch in Elm, I end up repeating a few first steps from scratch:
- __Routing__ - Implementing `onUrlChange` and `onUrlRequest` for my `update` function.
- __Page Transitions__ - Fading in the whole app on load and fading in/out the pages (not the persistent stuff) on route change.
- __Wiring up pages__ - Every page has it's own `Model`, `Msg`, `init`, `update`, `view`, and `subscriptions`. I need to bring those together at the top level.
- __Sharing app model__ - In addition to updating the _page model_, I need a way for pages to update the _shared app model_ used across pages (signed in users).
This package is an attempt to create a few abstractions on top of `Browser.application` to make creating single page applications focus on what makes __your app unique__.
## is it real?
- A working demo is available online here: [https://elm-app-demo.netlify.com/](https://elm-app-demo.netlify.com/)
- And you can play around with an example yourself in the repo: [https://github.com/ryannhg/elm-app/tree/master/examples/basic](https://github.com/ryannhg/elm-app/tree/master/examples/basic) around with the files in `examples/basic/src` and change things!
---
## examples are helpful!
Let's walk through the package together, at a high-level, with some code!
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
### src/Main.elm
2019-10-05 01:16:02 +03:00
```
2019-10-08 08:22:17 +03:00
our-project/
elm.json
src/
Main.elm ✨
2019-10-05 01:16:02 +03:00
```
2019-10-08 08:22:17 +03:00
This is the __entrypoint__ to the application, and connects all the parts of our `Application` together:
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
```elm
module Main exposing (main)
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
import Application
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
main =
Application.create
{ routing = -- TODO
, layout = -- TODO
, pages = -- TODO
}
```
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
As you can see, `Application.create` is a function that takes in a `record` with three properties:
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
1. __routing__ - handles URLs and page transitions
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
2. __layout__ - the app-level `init`, `update`, `view`, etc.
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
3. __pages__ - the page-level `init`, `update`, `view`, etc.
2019-10-05 01:16:02 +03:00
2019-10-05 01:18:12 +03:00
2019-10-08 08:22:17 +03:00
#### routing
2019-10-05 01:16:02 +03:00
```elm
module Main exposing (main)
2019-10-08 08:22:17 +03:00
import Application
import Route ✨
2019-10-05 01:16:02 +03:00
main =
2019-10-08 08:22:17 +03:00
Application.create
{ routing =
{ fromUrl = Route.fromUrl ✨
, toPath = Route.toPath ✨
, transitionSpeed = 200 ✨
2019-10-05 01:16:02 +03:00
}
2019-10-08 08:22:17 +03:00
, layout = -- TODO
, pages = -- TODO
}
2019-10-05 01:16:02 +03:00
```
2019-10-08 08:22:17 +03:00
The record for `routing` only has three properties:
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
1. __fromUrl__ - a function that turns a `Url` into a `Route`
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
2. __toPath__ - a function that turns a `Route` into a `String` used for links.
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
3. __transitionSpeed__ - number of __milliseconds__ it takes to fade in/out pages.
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
The implementation for `fromUrl` and `toPath` don't come from the `src/Main.elm`. Instead we create a new file called `src/Route.elm`, which handles all this for us in one place!
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
We'll link to that in a bit!
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
#### layout
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
```elm
module Main exposing (main)
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
import Application
import Route
import Components.Layout as Layout ✨
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
main =
Application.create
{ routing =
{ fromUrl = Route.fromUrl
, toPath = Route.toPath
, transitionSpeed = 200
}
, layout =
{ init = Layout.init ✨
, update = Layout.update ✨
, view = Layout.view ✨
, subscriptions = Layout.subscriptions ✨
}
, pages = -- TODO
}
2019-10-05 01:16:02 +03:00
```
2019-10-08 08:22:17 +03:00
The `layout` property introduces four new pieces:
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
1. __init__ - how to initialize the shared state.
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
2. __update__ - how to update the app-level state (and routing commands).
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
3. __view__ - the app-level view (and where to render our page view)
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
4. __subscriptions__ - app-level subscriptions (regardless of which page we're on)
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
Just like before, a new file `src/Components/Layout.elm` will contain all the functions we'll need for the layout, so that `Main.elm` is relatively focused.
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
#### pages
```elm
module Main exposing (main)
import Application
import Route
import Components.Layout as Layout
import Pages ✨
main =
Application.create
{ routing =
{ fromUrl = Route.fromUrl
, toPath = Route.toPath
, transitionSpeed = 200
}
, layout =
{ init = Layout.init
, update = Layout.update
, view = Layout.view
, subscriptions = Layout.subscriptions
}
, pages =
{ init = Pages.init ✨
, update = Pages.update ✨
, bundle = Pages.bundle ✨
}
2019-10-05 04:25:25 +03:00
}
2019-10-08 08:22:17 +03:00
```
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
Much like the last property, `pages` is just a few functions.
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
The `init` and `update` parts are fairly the same, but there's a new property that might look strange: `bundle`.
The "bundle" is a combination of `view`, `title`, `subscriptions` that allows our new `src/Pages.elm` file to reduce a bit of boilerplate! (There's a better explanation in the `src/Pages.elm` section of the guide.)
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
#### that's it for Main.elm!
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
As the final touch, we can update our import statements to add in a type annotation for the `main` function:
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
```elm
module Main exposing (main)
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
import Application exposing (Application) ✨
import Flags exposing (Flags) ✨
import Global ✨
import Route exposing (Route) ✨
import Components.Layout as Layout
import Pages
main : Application Flags Route Global.Model Global.Msg Pages.Model Pages.Msg ✨
main =
Application.create
{ routing =
{ fromUrl = Route.fromUrl
, toPath = Route.toPath
, transitionSpeed = 200
}
, layout =
{ init = Layout.init
, update = Layout.update
, view = Layout.view
, subscriptions = Layout.subscriptions
}
, pages =
{ init = Pages.init
, update = Pages.update
, bundle = Pages.bundle
}
2019-10-05 04:25:25 +03:00
}
2019-10-08 08:22:17 +03:00
```
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
Instead of main being the traditional `Program Flags Model Msg` type, here we use `Application Flags Route Global.Model Global.Msg Pages.Model Pages.Msg`, which is very long and spooky!
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
This is caused by the fact that our `Application.create` needs to know more about the `Flags`, `Route`, `Global`, and `Pages` types so it can do work for us.
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
But enough of that let's move on to routing next!
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
---
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
### src/Route.elm
2019-10-05 04:25:25 +03:00
2019-10-05 01:16:02 +03:00
```
2019-10-08 08:22:17 +03:00
our-project/
elm.json
src/
Main.elm
Route.elm ✨
```
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
in our new file, we need to create a [custom type](#custom-type) to handle all the possible routes.
```elm
module Route exposing (Route(..))
type Route
= Homepage
2019-10-08 08:30:58 +03:00
| SignIn
2019-10-08 08:22:17 +03:00
```
2019-10-05 01:16:02 +03:00
2019-10-08 08:30:58 +03:00
For now, there is only two routes: `Homepage` and `SignIn`.
2019-10-05 01:16:02 +03:00
2019-10-08 08:30:58 +03:00
We also need to make `fromUrl` and `toPath` so our application handles routing and page navigation correctly!
2019-10-08 08:22:17 +03:00
For that, we need to install the official `elm/url` package:
```
elm install elm/url
```
And use the newly installed `Url` and `Url.Parser` modules like this:
2019-10-05 01:16:02 +03:00
```elm
2019-10-08 08:22:17 +03:00
module Route exposing
( Route(..)
, fromUrl ✨
, toPath ✨
)
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
import Url exposing (Url) ✨
import Url.Parser as Parser exposing (Parser) ✨
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
type Route
= Homepage
2019-10-08 08:30:58 +03:00
| SignIn
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
fromUrl : Url -> Route ✨
-- TODO
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
toPath : Route -> String ✨
-- TODO
```
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
#### fromUrl
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
Let's get started on implementing `fromUrl` by using the `Parser` module:
```elm
type Route
= Homepage
2019-10-08 08:30:58 +03:00
| SignIn
2019-10-08 08:22:17 +03:00
| NotFound ✨ -- see note #2
fromUrl : Url -> Route
fromUrl url =
let
router =
2019-10-08 08:30:58 +03:00
Parser.oneOf ✨ -- see note #1
[ Parser.map Homepage Parser.top
, Parser.map SignIn (Parser.s "sign-in")
2019-10-08 08:22:17 +03:00
]
in
Parser.parse router url
|> Maybe.withDefault NotFound ✨ -- see note #2
```
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
__Notes__
2019-10-05 04:25:25 +03:00
2019-10-08 08:30:58 +03:00
1. With `Parser.oneOf`, we match `/` to `Homepage` and `/sign-in` to `SignIn`.
2019-10-08 08:22:17 +03:00
2019-10-08 08:24:14 +03:00
2. `Parser.parse` returns a `Maybe Route` because it not find a match in our `router`. That means we need to add a `NotFound` case (good catch, Elm!)
2019-10-08 08:22:17 +03:00
2019-10-08 08:30:58 +03:00
2019-10-08 08:22:17 +03:00
#### toPath
It turns out `toPath` is really easy, its just a case expression:
```elm
toPath : Route -> String ✨
toPath route =
case route of
Homepage -> "/"
2019-10-08 08:30:58 +03:00
SignIn -> "/sign-in"
2019-10-08 08:22:17 +03:00
NotFound -> "/not-found"
2019-10-05 04:25:25 +03:00
```
2019-10-08 08:24:45 +03:00
#### that's it for Route.elm!
2019-10-08 08:22:17 +03:00
here's the complete file we made.
```elm
module Route exposing
( Route(..)
, fromUrl
, toPath
)
import Url exposing (Url)
import Url.Parser as Parser exposing (Parser)
type Route
= Homepage
2019-10-08 08:30:58 +03:00
| SignIn
2019-10-08 08:22:17 +03:00
| NotFound
fromUrl : Url -> Route
fromUrl url =
let
router =
Parser.oneOf
[ Parser.map Homepage Parser.top
2019-10-08 08:30:58 +03:00
, Parser.map SignIn (Parser.s "sign-in")
2019-10-08 08:22:17 +03:00
]
in
Parser.parse router url
|> Maybe.withDefault NotFound
toPath : Route -> String
toPath route =
case route of
Homepage -> "/"
2019-10-08 08:30:58 +03:00
SignIn -> "/sign-in"
2019-10-08 08:22:17 +03:00
NotFound -> "/not-found"
```
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
You can learn how to add more routes by looking at:
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
1. __the `elm/url` docs__ - https://package.elm-lang.org/packages/elm/url/latest
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
2. __the example in this repo__ -https://github.com/ryannhg/elm-app/blob/master/examples/basic/src/Route.elm
---
### src/Flags.elm
```
our-project/
elm.json
src/
Main.elm
Route.elm
Flags.elm ✨
```
2019-10-08 08:30:58 +03:00
For this app, we don't actually have flags, so we return an empty tuple.
2019-10-05 04:25:25 +03:00
```elm
2019-10-08 08:22:17 +03:00
module Flags exposing (Flags)
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
type alias Flags = ()
```
2019-10-08 08:30:58 +03:00
So let's move onto something more interesting!
2019-10-08 08:22:17 +03:00
---
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
### src/Global.elm
```
our-project/
elm.json
src/
Main.elm
Route.elm
Flags.elm
2019-10-11 10:53:11 +03:00
Global.elm
Ports.elm ✨
2019-10-08 08:22:17 +03:00
```
2019-10-08 08:30:58 +03:00
The purpose of `Global.elm` is to define the `Model` and `Msg` types we'll share across pages and use in our layout functions:
2019-10-08 08:22:17 +03:00
```elm
module Global exposing ( Model, Msg(..) )
2019-10-05 01:16:02 +03:00
type alias Model =
2019-10-08 08:22:17 +03:00
{ isSignedIn : Bool
2019-10-05 04:25:25 +03:00
}
2019-10-05 01:16:02 +03:00
2019-10-05 04:25:25 +03:00
type Msg
2019-10-08 08:22:17 +03:00
= SignIn
2019-10-05 04:25:25 +03:00
| SignOut
2019-10-11 10:53:11 +03:00
| Log String
2019-10-08 08:22:17 +03:00
```
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
Here we create a simple record to keep track of the user's sign in status.
2019-10-05 01:16:02 +03:00
2019-10-08 08:30:58 +03:00
Let's see an example of `Global.Model` and `Global.Msg` being used in our layout:
2019-10-05 01:16:02 +03:00
2019-10-11 10:53:11 +03:00
#### src/Ports.elm
```
our-project/
elm.json
src/
Main.elm
Route.elm
Flags.elm
Global.elm
Ports.elm ✨
```
If you need to use ports, create a file called `src/Ports.elm`
```elm
port module Ports exposing (log)
port log : String -> Cmd msg
```
We'll use them in the layouts component up next!
2019-10-05 01:16:02 +03:00
2019-10-08 08:22:17 +03:00
### src/Components/Layout.elm
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
```
our-project/
elm.json
src/
Main.elm
Route.elm
Flags.elm
Global.elm
2019-10-11 10:53:11 +03:00
Ports.elm
2019-10-08 08:22:17 +03:00
Components/
Layout.elm ✨
```
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
To implement an app-level layout, we'll need a new file:
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
```elm
module Components.Layout exposing (init, update, view, subscriptions)
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
import Global
import Route exposing (Route)
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
-- ...
```
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
This file needs to export the following four functions:
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
#### init
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
```elm
init :
2019-10-08 17:02:25 +03:00
{ flags : Flags
2019-10-08 08:22:17 +03:00
, route : Route
2019-10-08 17:02:25 +03:00
, navigateTo : Route -> Cmd msg
2019-10-08 08:22:17 +03:00
}
-> ( Global.Model, Cmd Global.Msg, Cmd msg )
init _ =
( { isSignedIn = False }
, Cmd.none
, Cmd.none
)
```
2019-10-05 04:25:25 +03:00
2019-10-08 08:30:58 +03:00
Initially, our layout has access to a record with three fields:
2019-10-05 04:25:25 +03:00
2019-10-08 17:02:25 +03:00
- __flags__ - the initial JSON passed in with the app.
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
- __route__ - the current route
2019-10-05 04:25:25 +03:00
2019-10-08 17:02:25 +03:00
- __navigateTo__ - allows programmatic navigation to other pages.
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
For our example, we set `isSignedIn` to `False`, don't perform any `Global.Msg` side effects, nor use `messages.navigateTo` to change to another page.
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
#### update
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
```elm
2019-10-11 10:53:11 +03:00
import Ports ✨
2019-10-08 08:22:17 +03:00
update :
2019-10-08 17:02:25 +03:00
{ flags : Flags
2019-10-08 08:22:17 +03:00
, route : Route
2019-10-08 17:02:25 +03:00
, navigateTo : Route -> Cmd msg
2019-10-08 08:22:17 +03:00
}
-> Global.Msg
-> Global.Model
-> ( Global.Model, Cmd Global.Msg, Cmd msg )
update { navigateTo } msg model =
case msg of
Global.SignIn ->
( { model | isSignedIn = True }
, Cmd.none
, navigateTo Route.Homepage
)
Global.SignOut ->
( { model | isSignedIn = False }
, Cmd.none
, navigateTo Route.SignIn
)
2019-10-11 10:53:11 +03:00
Log message ->
( model
, Cmd.none
, Ports.log message
)
2019-10-05 04:25:25 +03:00
```
2019-10-08 08:30:58 +03:00
In addition to the record we saw earlier with `init`, our layout's `update` function take a `Global.Msg` and `Global.Model`:
2019-10-08 08:22:17 +03:00
2019-10-08 08:30:58 +03:00
That allows us to return an updated state of the app, and programmatically navigate to different pages!
2019-10-08 08:22:17 +03:00
2019-10-08 17:02:25 +03:00
#### view
```elm
view :
{ flags : Flags
, route : Route
, viewPage : Html msg
, toMsg : Global.Msg -> msg
}
-> Global.Model
-> Html msg
view { viewPage, toMsg } model =
div [ class "layout" ]
[ Html.map toMsg (viewNavbar model)
, viewPage
, viewFooter
]
```
Instead of `navigateTo`, our `view` function takes in a record with two other properties:
2. __viewPage__ - where we want the rendered page to show up in our layout
3. __toMsg__ - a way to convert from `Global.Msg` to `msg`, so that components can send global messages, but still return `Html msg`.
The `viewNavbar` function is an example of where we would use `Html.map toMsg` to turn `Html Global.Msg` into `Html msg`:
```elm
viewNavbar : Global.Model -> Html Global.Msg
viewNavbar model =
header
[ class "navbar" ]
[ a [ href (Route.toPath Route.Homepage) ]
[ text "Home" ]
, if model.isSignedIn then
button
[ Events.onClick SignOut ]
[ text "Sign out" ]
else
button
[ Events.onClick SignIn ]
[ text "Sign in" ]
]
```
The `viewFooter` function doesn't send messages, so `Html.map toMsg` isn't necessary!
```elm
viewFooter : Html msg
viewFooter =
footer
[ class "footer" ]
[ text "Build with Elm in 2019" ]
```
If you'd like, you can update the view to use components in folders like this:
```
our-project/
elm.json
src/
Main.elm
Route.elm
Flags.elm
Global.elm
2019-10-11 10:53:11 +03:00
Ports.elm
2019-10-08 17:02:25 +03:00
Components/
Layout.elm
Navbar.elm ✨
Footer.elm ✨
```
```elm
import Components.Navbar as Navbar ✨
import Components.Footer as Footer ✨
-- ...
view :
{ flags : Flags
, route : Route
, viewPage : Html msg
, toMsg : Global.Msg -> msg
}
-> Global.Model
-> Html msg
view { viewPage, toMsg } model =
div [ class "layout" ]
[ Html.map toMsg (Navbar.view model) ✨
, viewPage
, Footer.view ✨
]
```
Moving `Components.Layout.viewNavbar` into `Components.Navbar.view`
#### subscriptions
```elm
subscriptions :
{ navigateTo : Route -> Cmd msg
, route : Route
, flags : Flags
}
-> Global.Model
-> Html Global.Msg
subscriptions _ model =
Sub.none
```
That's the entire file! Here it is
2019-10-05 04:25:25 +03:00
2019-10-08 08:22:17 +03:00
---
2019-10-05 04:25:25 +03:00
2019-10-08 17:02:25 +03:00
### src/Pages.elm
```
our-project/
elm.json
src/
Main.elm
Route.elm
Flags.elm
Global.elm
2019-10-11 10:53:11 +03:00
Ports.elm
2019-10-08 17:02:25 +03:00
Pages.elm ✨
Components/
Layout.elm
Navbar.elm
Footer.elm
```
```elm
module Pages exposing (init, update, bundle)
import Pages.Homepage
import Pages.SignIn
import Pages.NotFound
type Model
= HomepageModel ()
| SignInModel Pages.SignIn.Model
| NotFoundModel ()
type Msg
= HomepageMsg Never
2019-10-08 17:02:56 +03:00
| SignInMsg Pages.SignIn.Msg
2019-10-08 17:02:25 +03:00
| NotFoundMsg Never
2019-10-08 23:06:03 +03:00
pages = -- TODO
2019-10-08 17:02:25 +03:00
init = -- TODO
update = -- TODO
bundle = -- TODO
```
2019-10-08 23:06:03 +03:00
Here we define a top level `Model` and `Msg`, so we can easily implement `init`, `update`, and `bundle`.
### pages
```elm
import Application.Page as Page ✨
pages =
{ homepage =
Page.static
{ title = Pages.Homepage.title
, view = Pages.Homepage.view
, toModel = HomepageModel
}
, signIn =
Page.page
{ title = Pages.SignIn.title
, init = Pages.SignIn.init
, update = Pages.SignIn.update
, subscriptions = Pages.SignIn.subscriptions
, view = Pages.SignIn.view
, toModel = SignInModel
, toMsg = SignInMsg
}
, notFound =
Page.static
{ title = Pages.NotFound.title
, view = Pages.NotFound.view
, toModel = NotFoundModel
}
}
```
The `Page` type is the important abstraction that allows us to make our `init` function take in the same shape.
#### init
```elm
2019-10-12 04:09:05 +03:00
import Application ✨
2019-10-08 23:06:03 +03:00
import Flags exposing (Flags) ✨
import Global ✨
import Route exposing (Route) ✨
init :
2019-10-11 10:45:31 +03:00
Route
2019-10-12 04:09:05 +03:00
-> Application.Update Flags Route Global.Model Global.Msg Model Msg
2019-10-11 10:45:31 +03:00
init route =
case route of
2019-10-08 23:06:03 +03:00
Route.Homepage ->
Application.init
{ page = pages.homepage
}
Route.SignIn ->
Application.init
{ page = pages.signIn
}
Route.NotFound ->
Application.init
{ page = pages.notFound
}
```
#### update
```elm
update :
2019-10-11 10:45:31 +03:00
Msg
2019-10-08 23:06:03 +03:00
-> Model
2019-10-12 04:09:05 +03:00
-> Application.Update Flags Route Global.Model Global.Msg Model Msg
2019-10-11 10:45:31 +03:00
update appMsg appModel =
2019-10-08 23:06:03 +03:00
case ( appModel, appMsg ) of
( HomepageModel model, HomepageMsg msg ) ->
Application.update
{ page = pages.homepage
, msg = msg
, model = model
}
( HomepageModel _, _ ) ->
2019-10-11 10:48:51 +03:00
Application.keep appModel
2019-10-08 23:06:03 +03:00
( SignInModel model, SignInMsg msg ) ->
Application.update
{ page = pages.signIn
, msg = msg
, model = model
}
( SignInModel _, _ ) ->
2019-10-11 10:48:51 +03:00
Application.keep appModel
2019-10-08 23:06:03 +03:00
( NotFoundModel model, NotFoundMsg msg ) ->
Application.update
{ page = pages.notFound
, msg = msg
, model = model
}
( NotFoundModel _, _ ) ->
2019-10-11 10:48:51 +03:00
Application.keep appModel
2019-10-08 23:06:03 +03:00
```
#### bundle
```elm
bundle :
2019-10-11 10:45:31 +03:00
Model
2019-10-12 04:09:05 +03:00
-> Application.Bundle Flags Route Global.Model Msg
2019-10-11 10:45:31 +03:00
bundle appModel =
2019-10-08 23:06:03 +03:00
case appModel of
HomepageModel model ->
Application.bundle
{ page = pages.homepage
, model = model
}
SignInModel model ->
Application.bundle
{ page = pages.signIn
, model = model
}
NotFoundModel model ->
Application.bundle
{ page = pages.notFound
, model = model
}
```
Like with the last two examples, `Application.bundle` makes our case expression consistent. Behind the scenes, `bundle` is used to provide `view`, `subscriptions`, and `title`.
The alternative would look super repetitive:
```elm
-- AN IMPROVEMENT ON
2019-10-11 10:45:31 +03:00
view appModel =
2019-10-08 23:06:03 +03:00
case appModel of
HomepageModel model -> Application.view { ... }
SignInModel model -> Application.view { ... }
2019-10-11 07:16:02 +03:00
NotFoundModel model -> Application.view { ... }
2019-10-08 23:06:03 +03:00
2019-10-11 10:45:31 +03:00
title appModel =
2019-10-08 23:06:03 +03:00
case appModel of
HomepageModel model -> Application.title { ... }
SignInModel model -> Application.title { ... }
2019-10-11 07:16:02 +03:00
NotFoundModel model -> Application.title { ... }
2019-10-08 23:06:03 +03:00
2019-10-11 10:45:31 +03:00
subscriptions appModel =
2019-10-08 23:06:03 +03:00
case appModel of
HomepageModel model -> Application.subscriptions { ... }
SignInModel model -> Application.subscriptions { ... }
2019-10-11 07:16:02 +03:00
NotFoundModel model -> Application.subscriptions { ... }
2019-10-08 23:06:03 +03:00
```
The `bundle` abstraction gives us the ability to only write __one case expression__ at the top level for all three of these things 😎
You can find `src/Pages/*.elm` examples in the [basic example]. All those pages are really just normal Elm `init/update/view` things!
## that's it!
2019-10-05 04:25:25 +03:00
2019-10-08 23:06:03 +03:00
Thanks for reading this huge README, I hope this package helps you build great single page apps with Elm! 😄