mirror of
https://github.com/ryannhg/elm-spa.git
synced 2024-11-25 14:39:52 +03:00
add in example
This commit is contained in:
commit
3ce88f8e24
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.cache
|
||||
.DS_Store
|
||||
dist
|
||||
elm-stuff
|
||||
node_modules
|
192
README.md
Normal file
192
README.md
Normal file
@ -0,0 +1,192 @@
|
||||
# ryannhg/elm-app
|
||||
> a way to build single page apps with Elm.
|
||||
|
||||
## try it out
|
||||
|
||||
```
|
||||
elm install ryannhg/elm-app
|
||||
```
|
||||
|
||||
## quick overview
|
||||
|
||||
this package is a wrapper around Elm's `Browser.application`, adding in page transitions and utilities for adding in new pages and routes.
|
||||
|
||||
here's what it looks like to use it:
|
||||
|
||||
|
||||
### src/Main.elm
|
||||
|
||||
This is the entrypoint to the app, it imports:
|
||||
|
||||
- `Application` - this package
|
||||
- `App` - the top level `Model`, `Msg`, `init`, `update`, `subscriptions`, and `view`
|
||||
- `
|
||||
|
||||
```elm
|
||||
module Main exposing (main)
|
||||
|
||||
import Application exposing (Application)
|
||||
|
||||
import App
|
||||
import Context
|
||||
import Route
|
||||
import Flags exposing (Flags)
|
||||
|
||||
|
||||
main : Application Flags Context.Model Context.Msg App.Model App.Msg
|
||||
main =
|
||||
Application.create
|
||||
{ transition = 300
|
||||
, toRoute = Route.fromUrl
|
||||
, title = Route.title
|
||||
, context =
|
||||
{ init = Context.init
|
||||
, update = Context.update
|
||||
, view = Context.view
|
||||
, subscriptions = Context.subscriptions
|
||||
}
|
||||
, page =
|
||||
{ init = App.init
|
||||
, update = App.update
|
||||
, view = App.view
|
||||
, subscriptions = App.subscriptions
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### src/Pages/Homepage.elm
|
||||
> Uses `Application.Page.static`
|
||||
|
||||
The homepage is static, so it's just a `view`:
|
||||
|
||||
```elm
|
||||
module Pages.Homepage exposing (view)
|
||||
|
||||
import Html exposing (Html)
|
||||
|
||||
|
||||
view : Html Never
|
||||
view =
|
||||
Html.text "Homepage!"
|
||||
```
|
||||
|
||||
|
||||
### src/Pages/Counter.elm
|
||||
> Uses `Application.Page.Sandbox`
|
||||
|
||||
The counter page doesn't have any side effects:
|
||||
|
||||
```elm
|
||||
module Pages.Counter exposing (Model, Msg, init, update, view)
|
||||
|
||||
import Html exposing (..)
|
||||
import Html.Events as Events
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ counter : Int
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Increment
|
||||
| Decrement
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ counter = 0
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> Model
|
||||
update msg model =
|
||||
case msg of
|
||||
Decrement ->
|
||||
{ model | counter = model.counter - 1 }
|
||||
|
||||
Increment ->
|
||||
{ model | counter = model.counter + 1 }
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div []
|
||||
[ button [ Events.onClick Decrement ] [ text "-" ]
|
||||
, text (String.fromInt model.counter)
|
||||
, button [ Events.onClick Increment ] [ text "+" ]
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
### src/Pages/Random.elm
|
||||
> Uses `Application.Page.element`
|
||||
|
||||
The random page doesn't need to update the context of the application:
|
||||
|
||||
```elm
|
||||
module Pages.Random exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, subscriptions
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Events as Events
|
||||
import Random
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ roll : Maybe Int
|
||||
}
|
||||
|
||||
type Msg
|
||||
= Roll
|
||||
| GotOutcome Int
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init _ =
|
||||
( { roll = Nothing }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
rollDice : Model -> ( Model, Cmd Msg )
|
||||
rollDice model =
|
||||
( model
|
||||
, Random.generate GotOutcome (Random.int 1 6)
|
||||
)
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Roll ->
|
||||
rollDice model
|
||||
|
||||
GotOutcome value ->
|
||||
( { model | roll = Just value }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div []
|
||||
[ button [ Events.onClick Roll ] [ text "Roll" ]
|
||||
, p []
|
||||
[ case model.roll of
|
||||
Just roll ->
|
||||
text (String.fromInt roll)
|
||||
Nothing ->
|
||||
text "Click the button!"
|
||||
]
|
||||
]
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
Sub.none
|
||||
```
|
24
elm.json
Normal file
24
elm.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src"
|
||||
],
|
||||
"elm-version": "0.19.0",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/browser": "1.0.1",
|
||||
"elm/core": "1.0.2",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/url": "1.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
4
examples/README.md
Normal file
4
examples/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# examples
|
||||
> some examples of how to use this package.
|
||||
|
||||
- [basic](./basic)
|
8
examples/basic/README.md
Normal file
8
examples/basic/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# examples/basic
|
||||
> an intro into an app using `ryannhg/elm-app`
|
||||
|
||||
## how to run
|
||||
|
||||
1. `npm install`
|
||||
|
||||
1. `npm run dev`
|
26
examples/basic/elm.json
Normal file
26
examples/basic/elm.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src",
|
||||
"../../src"
|
||||
],
|
||||
"elm-version": "0.19.0",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/browser": "1.0.1",
|
||||
"elm/core": "1.0.2",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/url": "1.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
13
examples/basic/index.html
Normal file
13
examples/basic/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
5
examples/basic/main.js
Normal file
5
examples/basic/main.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { Elm } from './src/Main.elm'
|
||||
|
||||
Elm.Main.init({
|
||||
node: document.getElementById('app')
|
||||
})
|
7585
examples/basic/package-lock.json
generated
Normal file
7585
examples/basic/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
examples/basic/package.json
Normal file
19
examples/basic/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "application",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "parcel index.html"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"elm": "^0.19.0-no-deps",
|
||||
"elm-format": "^0.8.2",
|
||||
"elm-hot": "^1.1.2",
|
||||
"node-elm-compiler": "^5.0.4",
|
||||
"parcel-bundler": "^1.12.3"
|
||||
}
|
||||
}
|
223
examples/basic/src/App.elm
Normal file
223
examples/basic/src/App.elm
Normal file
@ -0,0 +1,223 @@
|
||||
module App exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, subscriptions
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Application.Page as Page exposing (Context)
|
||||
import Context
|
||||
import Flags exposing (Flags)
|
||||
import Html exposing (Html)
|
||||
import Pages.Counter
|
||||
import Pages.Homepage
|
||||
import Pages.NotFound
|
||||
import Pages.Random
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
type Model
|
||||
= HomepageModel ()
|
||||
| CounterModel Pages.Counter.Model
|
||||
| RandomModel Pages.Random.Model
|
||||
| NotFoundModel ()
|
||||
|
||||
|
||||
type Msg
|
||||
= HomepageMsg Never
|
||||
| CounterMsg Pages.Counter.Msg
|
||||
| RandomMsg Pages.Random.Msg
|
||||
| NotFoundMsg Never
|
||||
|
||||
|
||||
pages =
|
||||
{ homepage =
|
||||
Page.static
|
||||
{ view = Pages.Homepage.view
|
||||
, toModel = HomepageModel
|
||||
}
|
||||
, counter =
|
||||
Page.sandbox
|
||||
{ init = Pages.Counter.init
|
||||
, update = Pages.Counter.update
|
||||
, view = Pages.Counter.view
|
||||
, toModel = CounterModel
|
||||
, toMsg = CounterMsg
|
||||
}
|
||||
, random =
|
||||
Page.element
|
||||
{ init = Pages.Random.init
|
||||
, update = Pages.Random.update
|
||||
, subscriptions = Pages.Random.subscriptions
|
||||
, view = Pages.Random.view
|
||||
, toModel = RandomModel
|
||||
, toMsg = RandomMsg
|
||||
}
|
||||
, notFound =
|
||||
Page.static
|
||||
{ view = Pages.NotFound.view
|
||||
, toModel = NotFoundModel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init :
|
||||
Context Flags Route Context.Model
|
||||
-> ( Model, Cmd Msg, Cmd Context.Msg )
|
||||
init context =
|
||||
case context.route of
|
||||
Route.Homepage ->
|
||||
Page.init
|
||||
{ page = pages.homepage }
|
||||
context
|
||||
|
||||
Route.Counter ->
|
||||
Page.init
|
||||
{ page = pages.counter }
|
||||
context
|
||||
|
||||
Route.Random ->
|
||||
Page.init
|
||||
{ page = pages.random }
|
||||
context
|
||||
|
||||
Route.NotFound ->
|
||||
Page.init
|
||||
{ page = pages.notFound }
|
||||
context
|
||||
|
||||
|
||||
update :
|
||||
Context Flags Route Context.Model
|
||||
-> Msg
|
||||
-> Model
|
||||
-> ( Model, Cmd Msg, Cmd Context.Msg )
|
||||
update context appMsg appModel =
|
||||
case ( appModel, appMsg ) of
|
||||
( HomepageModel model, HomepageMsg msg ) ->
|
||||
Page.update
|
||||
{ page = pages.homepage
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
( HomepageModel _, _ ) ->
|
||||
( appModel
|
||||
, Cmd.none
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
( CounterModel model, CounterMsg msg ) ->
|
||||
Page.update
|
||||
{ page = pages.counter
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
( CounterModel _, _ ) ->
|
||||
( appModel
|
||||
, Cmd.none
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
( RandomModel model, RandomMsg msg ) ->
|
||||
Page.update
|
||||
{ page = pages.random
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
( RandomModel _, _ ) ->
|
||||
( appModel
|
||||
, Cmd.none
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
( NotFoundModel model, NotFoundMsg msg ) ->
|
||||
Page.update
|
||||
{ page = pages.notFound
|
||||
, msg = msg
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
( NotFoundModel _, _ ) ->
|
||||
( appModel
|
||||
, Cmd.none
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions :
|
||||
Context Flags Route Context.Model
|
||||
-> Model
|
||||
-> Sub Msg
|
||||
subscriptions context appModel =
|
||||
case appModel of
|
||||
HomepageModel model ->
|
||||
Page.subscriptions
|
||||
{ page = pages.homepage
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
CounterModel model ->
|
||||
Page.subscriptions
|
||||
{ page = pages.counter
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
RandomModel model ->
|
||||
Page.subscriptions
|
||||
{ page = pages.random
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
NotFoundModel model ->
|
||||
Page.subscriptions
|
||||
{ page = pages.notFound
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
|
||||
view :
|
||||
Context Flags Route Context.Model
|
||||
-> Model
|
||||
-> Html Msg
|
||||
view context appModel =
|
||||
case appModel of
|
||||
HomepageModel model ->
|
||||
Page.view
|
||||
{ page = pages.homepage
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
CounterModel model ->
|
||||
Page.view
|
||||
{ page = pages.counter
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
RandomModel model ->
|
||||
Page.view
|
||||
{ page = pages.random
|
||||
, model = model
|
||||
}
|
||||
context
|
||||
|
||||
NotFoundModel model ->
|
||||
Page.view
|
||||
{ page = pages.notFound
|
||||
, model = model
|
||||
}
|
||||
context
|
108
examples/basic/src/Context.elm
Normal file
108
examples/basic/src/Context.elm
Normal file
@ -0,0 +1,108 @@
|
||||
module Context exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, subscriptions
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ user : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= SignIn String
|
||||
| SignOut
|
||||
|
||||
|
||||
init : Route -> Flags -> ( Model, Cmd Msg )
|
||||
init route flags =
|
||||
( { user = Nothing }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update : Route -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update route msg model =
|
||||
case msg of
|
||||
SignIn user ->
|
||||
( { model | user = Just user }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
SignOut ->
|
||||
( { model | user = Nothing }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
view :
|
||||
{ route : Route
|
||||
, context : Model
|
||||
, toMsg : Msg -> msg
|
||||
, viewPage : Html msg
|
||||
}
|
||||
-> Html msg
|
||||
view { context, route, toMsg, viewPage } =
|
||||
div [ Attr.class "layout" ]
|
||||
[ Html.map toMsg (viewNavbar route context)
|
||||
, br [] []
|
||||
, viewPage
|
||||
, br [] []
|
||||
, Html.map toMsg (viewFooter context)
|
||||
]
|
||||
|
||||
|
||||
viewNavbar : Route -> Model -> Html Msg
|
||||
viewNavbar currentRoute model =
|
||||
header [ Attr.class "navbar" ]
|
||||
[ ul []
|
||||
(List.map
|
||||
(viewLink currentRoute)
|
||||
[ Route.Homepage, Route.Counter, Route.Random ]
|
||||
)
|
||||
, case model.user of
|
||||
Just _ ->
|
||||
button [ Events.onClick SignOut ] [ text <| "Sign out" ]
|
||||
|
||||
Nothing ->
|
||||
button [ Events.onClick (SignIn "Ryan") ] [ text "Sign in" ]
|
||||
]
|
||||
|
||||
|
||||
viewLink : Route -> Route -> Html msg
|
||||
viewLink currentRoute route =
|
||||
li []
|
||||
[ a
|
||||
[ Attr.href (Route.toPath route)
|
||||
, Attr.style "font-weight"
|
||||
(if route == currentRoute then
|
||||
"bold"
|
||||
|
||||
else
|
||||
"normal"
|
||||
)
|
||||
]
|
||||
[ text (Route.title route) ]
|
||||
]
|
||||
|
||||
|
||||
viewFooter : Model -> Html Msg
|
||||
viewFooter model =
|
||||
footer [ Attr.class "footer" ]
|
||||
[ model.user |> Maybe.withDefault "Not signed in" |> text
|
||||
]
|
||||
|
||||
|
||||
subscriptions : Route -> Model -> Sub Msg
|
||||
subscriptions route model =
|
||||
Sub.none
|
5
examples/basic/src/Flags.elm
Normal file
5
examples/basic/src/Flags.elm
Normal file
@ -0,0 +1,5 @@
|
||||
module Flags exposing (Flags)
|
||||
|
||||
|
||||
type alias Flags =
|
||||
()
|
32
examples/basic/src/Main.elm
Normal file
32
examples/basic/src/Main.elm
Normal file
@ -0,0 +1,32 @@
|
||||
module Main exposing (main)
|
||||
|
||||
import App
|
||||
import Application exposing (Application)
|
||||
import Application.Page exposing (Context)
|
||||
import Context
|
||||
import Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
main : Application Flags Context.Model Context.Msg App.Model App.Msg
|
||||
main =
|
||||
Application.create
|
||||
{ transition = 200
|
||||
, context =
|
||||
{ init = Context.init
|
||||
, update = Context.update
|
||||
, view = Context.view
|
||||
, subscriptions = Context.subscriptions
|
||||
}
|
||||
, page =
|
||||
{ init = App.init
|
||||
, update = App.update
|
||||
, view = App.view
|
||||
, subscriptions = App.subscriptions
|
||||
}
|
||||
, toRoute = Route.fromUrl
|
||||
, title = Route.title
|
||||
}
|
39
examples/basic/src/Pages/Counter.elm
Normal file
39
examples/basic/src/Pages/Counter.elm
Normal file
@ -0,0 +1,39 @@
|
||||
module Pages.Counter exposing (Model, Msg, init, update, view)
|
||||
|
||||
import Html exposing (..)
|
||||
import Html.Events as Events
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ counter : Int
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Increment
|
||||
| Decrement
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ counter = 0
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> Model
|
||||
update msg model =
|
||||
case msg of
|
||||
Decrement ->
|
||||
{ model | counter = model.counter - 1 }
|
||||
|
||||
Increment ->
|
||||
{ model | counter = model.counter + 1 }
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div []
|
||||
[ button [ Events.onClick Decrement ] [ text "-" ]
|
||||
, text (String.fromInt model.counter)
|
||||
, button [ Events.onClick Increment ] [ text "+" ]
|
||||
]
|
8
examples/basic/src/Pages/Homepage.elm
Normal file
8
examples/basic/src/Pages/Homepage.elm
Normal file
@ -0,0 +1,8 @@
|
||||
module Pages.Homepage exposing (view)
|
||||
|
||||
import Html exposing (Html)
|
||||
|
||||
|
||||
view : Html Never
|
||||
view =
|
||||
Html.text "Homepage!"
|
8
examples/basic/src/Pages/NotFound.elm
Normal file
8
examples/basic/src/Pages/NotFound.elm
Normal file
@ -0,0 +1,8 @@
|
||||
module Pages.NotFound exposing (view)
|
||||
|
||||
import Html exposing (Html)
|
||||
|
||||
|
||||
view : Html Never
|
||||
view =
|
||||
Html.text "Page not found..."
|
67
examples/basic/src/Pages/Random.elm
Normal file
67
examples/basic/src/Pages/Random.elm
Normal file
@ -0,0 +1,67 @@
|
||||
module Pages.Random exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
, subscriptions
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Events as Events
|
||||
import Random
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ roll : Maybe Int
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Roll
|
||||
| GotOutcome Int
|
||||
|
||||
|
||||
init : Flags -> ( Model, Cmd Msg )
|
||||
init _ =
|
||||
( { roll = Nothing }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
rollDice : Model -> ( Model, Cmd Msg )
|
||||
rollDice model =
|
||||
( model
|
||||
, Random.generate GotOutcome (Random.int 1 6)
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Roll ->
|
||||
rollDice model
|
||||
|
||||
GotOutcome value ->
|
||||
( { model | roll = Just value }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div []
|
||||
[ button [ Events.onClick Roll ] [ text "Roll" ]
|
||||
, p []
|
||||
[ model.roll
|
||||
|> Maybe.map String.fromInt
|
||||
|> Maybe.withDefault "Click the button!"
|
||||
|> text
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
Sub.none
|
58
examples/basic/src/Route.elm
Normal file
58
examples/basic/src/Route.elm
Normal file
@ -0,0 +1,58 @@
|
||||
module Route exposing (Route(..), fromUrl, title, toPath)
|
||||
|
||||
import Url exposing (Url)
|
||||
import Url.Parser as Parser exposing (Parser)
|
||||
|
||||
|
||||
type Route
|
||||
= Homepage
|
||||
| Counter
|
||||
| Random
|
||||
| NotFound
|
||||
|
||||
|
||||
fromUrl : Url -> Route
|
||||
fromUrl =
|
||||
Parser.parse router >> Maybe.withDefault NotFound
|
||||
|
||||
|
||||
router : Parser (Route -> Route) Route
|
||||
router =
|
||||
Parser.oneOf
|
||||
[ Parser.map Homepage Parser.top
|
||||
, Parser.map Counter (Parser.s "counter")
|
||||
, Parser.map Random (Parser.s "random")
|
||||
]
|
||||
|
||||
|
||||
toPath : Route -> String
|
||||
toPath route =
|
||||
(String.join "/" >> (++) "/") <|
|
||||
case route of
|
||||
Homepage ->
|
||||
[]
|
||||
|
||||
Counter ->
|
||||
[ "counter" ]
|
||||
|
||||
Random ->
|
||||
[ "random" ]
|
||||
|
||||
NotFound ->
|
||||
[ "not-found" ]
|
||||
|
||||
|
||||
title : Route -> String
|
||||
title route =
|
||||
case route of
|
||||
Homepage ->
|
||||
"Home"
|
||||
|
||||
Counter ->
|
||||
"Counter"
|
||||
|
||||
Random ->
|
||||
"Random"
|
||||
|
||||
NotFound ->
|
||||
"Not found"
|
306
src/Application.elm
Normal file
306
src/Application.elm
Normal file
@ -0,0 +1,306 @@
|
||||
module Application exposing (Application, create)
|
||||
|
||||
import Application.Page exposing (Context)
|
||||
import Browser
|
||||
import Browser.Navigation as Nav
|
||||
import Html exposing (Html, div)
|
||||
import Html.Attributes as Attr
|
||||
import Process
|
||||
import Task
|
||||
import Url exposing (Url)
|
||||
|
||||
|
||||
type alias Application flags contextModel contextMsg model msg =
|
||||
Program flags (Model flags contextModel model) (Msg contextMsg msg)
|
||||
|
||||
|
||||
type alias Model flags contextModel model =
|
||||
{ key : Nav.Key
|
||||
, url : Url
|
||||
, flags : flags
|
||||
, context : contextModel
|
||||
, page : Loadable model
|
||||
}
|
||||
|
||||
|
||||
type Loadable a
|
||||
= FirstLoad a
|
||||
| Loading a
|
||||
| Loaded a
|
||||
|
||||
|
||||
isFirstLoad : Loadable a -> Bool
|
||||
isFirstLoad loadable =
|
||||
case loadable of
|
||||
FirstLoad _ ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
unwrap : Loadable a -> a
|
||||
unwrap loadable =
|
||||
case loadable of
|
||||
FirstLoad a ->
|
||||
a
|
||||
|
||||
Loading a ->
|
||||
a
|
||||
|
||||
Loaded a ->
|
||||
a
|
||||
|
||||
|
||||
map : (a -> b) -> Loadable a -> Loadable b
|
||||
map fn loadable =
|
||||
case loadable of
|
||||
FirstLoad a ->
|
||||
FirstLoad (fn a)
|
||||
|
||||
Loading a ->
|
||||
Loading (fn a)
|
||||
|
||||
Loaded a ->
|
||||
Loaded (fn a)
|
||||
|
||||
|
||||
type Msg contextMsg msg
|
||||
= UrlChanged Url
|
||||
| UrlRequested Browser.UrlRequest
|
||||
| PageLoaded Url
|
||||
| ContextMsg contextMsg
|
||||
| PageMsg msg
|
||||
|
||||
|
||||
type alias Config flags route contextModel contextMsg model msg =
|
||||
{ context :
|
||||
{ init : route -> flags -> ( contextModel, Cmd contextMsg )
|
||||
, update : route -> contextMsg -> contextModel -> ( contextModel, Cmd contextMsg )
|
||||
, subscriptions : route -> contextModel -> Sub contextMsg
|
||||
, view :
|
||||
{ route : route
|
||||
, context : contextModel
|
||||
, toMsg : contextMsg -> Msg contextMsg msg
|
||||
, viewPage : Html (Msg contextMsg msg)
|
||||
}
|
||||
-> Html (Msg contextMsg msg)
|
||||
}
|
||||
, page :
|
||||
{ init : Context flags route contextModel -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, update : Context flags route contextModel -> msg -> model -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, subscriptions : Context flags route contextModel -> model -> Sub msg
|
||||
, view : Context flags route contextModel -> model -> Html msg
|
||||
}
|
||||
, toRoute : Url -> route
|
||||
, title : route -> String
|
||||
, transition : Float
|
||||
}
|
||||
|
||||
|
||||
create :
|
||||
Config flags route contextModel contextMsg model msg
|
||||
-> Application flags contextModel contextMsg model msg
|
||||
create config =
|
||||
Browser.application
|
||||
{ init = init config
|
||||
, update = update config
|
||||
, view = view config
|
||||
, subscriptions = subscriptions config
|
||||
, onUrlChange = UrlChanged
|
||||
, onUrlRequest = UrlRequested
|
||||
}
|
||||
|
||||
|
||||
init :
|
||||
Config flags route contextModel contextMsg model msg
|
||||
-> flags
|
||||
-> Url
|
||||
-> Nav.Key
|
||||
-> ( Model flags contextModel model, Cmd (Msg contextMsg msg) )
|
||||
init config flags url key =
|
||||
let
|
||||
route =
|
||||
config.toRoute url
|
||||
|
||||
( contextModel, contextCmd ) =
|
||||
config.context.init route flags
|
||||
|
||||
( pageModel, pageCmd, pageContextCmd ) =
|
||||
config.page.init
|
||||
{ route = route
|
||||
, flags = flags
|
||||
, context = contextModel
|
||||
}
|
||||
in
|
||||
( { url = url
|
||||
, key = key
|
||||
, flags = flags
|
||||
, context = contextModel
|
||||
, page = FirstLoad pageModel
|
||||
}
|
||||
, Cmd.batch
|
||||
[ delay config.transition (PageLoaded url)
|
||||
, Cmd.map ContextMsg contextCmd
|
||||
, Cmd.map ContextMsg pageContextCmd
|
||||
, Cmd.map PageMsg pageCmd
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
delay : Float -> msg -> Cmd msg
|
||||
delay ms msg =
|
||||
Task.perform (\_ -> msg) (Process.sleep ms)
|
||||
|
||||
|
||||
update :
|
||||
Config flags route contextModel contextMsg model msg
|
||||
-> Msg contextMsg msg
|
||||
-> Model flags contextModel model
|
||||
-> ( Model flags contextModel model, Cmd (Msg contextMsg msg) )
|
||||
update config msg model =
|
||||
case msg of
|
||||
UrlRequested urlRequest ->
|
||||
case urlRequest of
|
||||
Browser.Internal url ->
|
||||
( model
|
||||
, Nav.pushUrl model.key (Url.toString url)
|
||||
)
|
||||
|
||||
Browser.External url ->
|
||||
( model
|
||||
, Nav.load url
|
||||
)
|
||||
|
||||
UrlChanged url ->
|
||||
( { model | page = Loading (unwrap model.page) }
|
||||
, delay config.transition (PageLoaded url)
|
||||
)
|
||||
|
||||
PageLoaded url ->
|
||||
let
|
||||
( pageModel, pageCmd, contextCmd ) =
|
||||
config.page.init
|
||||
{ route = config.toRoute url
|
||||
, flags = model.flags
|
||||
, context = model.context
|
||||
}
|
||||
in
|
||||
( { model | url = url, page = Loaded pageModel }
|
||||
, Cmd.batch
|
||||
[ Cmd.map PageMsg pageCmd
|
||||
, Cmd.map ContextMsg contextCmd
|
||||
]
|
||||
)
|
||||
|
||||
ContextMsg msg_ ->
|
||||
Tuple.mapBoth
|
||||
(\context -> { model | context = context })
|
||||
(Cmd.map ContextMsg)
|
||||
(config.context.update (config.toRoute model.url) msg_ model.context)
|
||||
|
||||
PageMsg msg_ ->
|
||||
let
|
||||
( pageModel, pageCmd, contextCmd ) =
|
||||
config.page.update
|
||||
{ route = config.toRoute model.url
|
||||
, flags = model.flags
|
||||
, context = model.context
|
||||
}
|
||||
msg_
|
||||
(unwrap model.page)
|
||||
in
|
||||
( { model | page = map (always pageModel) model.page }
|
||||
, Cmd.batch
|
||||
[ Cmd.map ContextMsg contextCmd
|
||||
, Cmd.map PageMsg pageCmd
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
type alias Document msg =
|
||||
{ title : String
|
||||
, body : List (Html msg)
|
||||
}
|
||||
|
||||
|
||||
view :
|
||||
Config flags route contextModel contextMsg model msg
|
||||
-> Model flags contextModel model
|
||||
-> Document (Msg contextMsg msg)
|
||||
view config model =
|
||||
let
|
||||
transitionProp : Float -> String
|
||||
transitionProp ms =
|
||||
"opacity " ++ String.fromFloat ms ++ "ms ease-in-out"
|
||||
|
||||
layoutOpacity : Loadable a -> String
|
||||
layoutOpacity loadable =
|
||||
case loadable of
|
||||
FirstLoad _ ->
|
||||
"0"
|
||||
|
||||
Loading _ ->
|
||||
"1"
|
||||
|
||||
Loaded _ ->
|
||||
"1"
|
||||
|
||||
pageOpacity : Loadable a -> String
|
||||
pageOpacity loadable =
|
||||
case loadable of
|
||||
FirstLoad _ ->
|
||||
"0"
|
||||
|
||||
Loading _ ->
|
||||
"0"
|
||||
|
||||
Loaded _ ->
|
||||
"1"
|
||||
in
|
||||
{ title = config.title (config.toRoute model.url)
|
||||
, body =
|
||||
[ div
|
||||
[ Attr.style "transition" (transitionProp config.transition)
|
||||
, Attr.style "opacity" (layoutOpacity model.page)
|
||||
]
|
||||
[ config.context.view
|
||||
{ route = config.toRoute model.url
|
||||
, toMsg = ContextMsg
|
||||
, context = model.context
|
||||
, viewPage =
|
||||
div
|
||||
[ Attr.style "transition" (transitionProp config.transition)
|
||||
, Attr.style "opacity" (pageOpacity model.page)
|
||||
]
|
||||
[ Html.map PageMsg
|
||||
(config.page.view
|
||||
{ route = config.toRoute model.url
|
||||
, flags = model.flags
|
||||
, context = model.context
|
||||
}
|
||||
(unwrap model.page)
|
||||
)
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
subscriptions :
|
||||
Config flags route contextModel contextMsg model msg
|
||||
-> Model flags contextModel model
|
||||
-> Sub (Msg contextMsg msg)
|
||||
subscriptions config model =
|
||||
Sub.batch
|
||||
[ Sub.map ContextMsg (config.context.subscriptions (config.toRoute model.url) model.context)
|
||||
, Sub.map PageMsg
|
||||
(config.page.subscriptions
|
||||
{ route = config.toRoute model.url
|
||||
, flags = model.flags
|
||||
, context = model.context
|
||||
}
|
||||
(unwrap model.page)
|
||||
)
|
||||
]
|
185
src/Application/Page.elm
Normal file
185
src/Application/Page.elm
Normal file
@ -0,0 +1,185 @@
|
||||
module Application.Page exposing
|
||||
( Context
|
||||
, Page
|
||||
, element
|
||||
, init
|
||||
, page
|
||||
, sandbox
|
||||
, static
|
||||
, subscriptions
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Html exposing (Html)
|
||||
|
||||
|
||||
type alias Context flags route contextModel =
|
||||
{ flags : flags
|
||||
, route : route
|
||||
, context : contextModel
|
||||
}
|
||||
|
||||
|
||||
type alias Page route flags contextModel contextMsg model msg appModel appMsg =
|
||||
{ init : Context flags route contextModel -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, update : Context flags route contextModel -> msg -> model -> ( model, Cmd msg, Cmd contextMsg )
|
||||
, subscriptions : Context flags route contextModel -> model -> Sub msg
|
||||
, view : Context flags route contextModel -> model -> Html msg
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- PAGE HELPERS
|
||||
|
||||
|
||||
init :
|
||||
{ page : Page route flags contextModel contextMsg model msg appModel appMsg
|
||||
}
|
||||
-> Context flags route contextModel
|
||||
-> ( appModel, Cmd appMsg, Cmd contextMsg )
|
||||
init config context =
|
||||
config.page.init context
|
||||
|> mapTruple
|
||||
{ fromMsg = config.page.toMsg
|
||||
, fromModel = config.page.toModel
|
||||
}
|
||||
|
||||
|
||||
update :
|
||||
{ page : Page route flags contextModel contextMsg model msg appModel appMsg
|
||||
, msg : msg
|
||||
, model : model
|
||||
}
|
||||
-> Context flags route contextModel
|
||||
-> ( appModel, Cmd appMsg, Cmd contextMsg )
|
||||
update config context =
|
||||
config.page.update context config.msg config.model
|
||||
|> mapTruple
|
||||
{ fromMsg = config.page.toMsg
|
||||
, fromModel = config.page.toModel
|
||||
}
|
||||
|
||||
|
||||
subscriptions :
|
||||
{ page : Page route flags contextModel contextMsg model msg appModel appMsg
|
||||
, model : model
|
||||
}
|
||||
-> Context flags route contextModel
|
||||
-> Sub appMsg
|
||||
subscriptions config context =
|
||||
config.page.subscriptions context config.model
|
||||
|> Sub.map config.page.toMsg
|
||||
|
||||
|
||||
view :
|
||||
{ page : Page route flags contextModel contextMsg model msg appModel appMsg
|
||||
, model : model
|
||||
}
|
||||
-> Context flags route contextModel
|
||||
-> Html appMsg
|
||||
view config context =
|
||||
config.page.view context config.model
|
||||
|> Html.map config.page.toMsg
|
||||
|
||||
|
||||
|
||||
-- PAGE ADAPTERS
|
||||
|
||||
|
||||
static :
|
||||
{ view : Html Never
|
||||
, toModel : () -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg () Never appModel appMsg
|
||||
static config =
|
||||
{ init = \c -> ( (), Cmd.none, Cmd.none )
|
||||
, update = \c m model -> ( model, Cmd.none, Cmd.none )
|
||||
, subscriptions = \c m -> Sub.none
|
||||
, view = \c m -> Html.map never config.view
|
||||
, toMsg = never
|
||||
, toModel = config.toModel
|
||||
}
|
||||
|
||||
|
||||
sandbox :
|
||||
{ init : model
|
||||
, update : msg -> model -> model
|
||||
, view : model -> Html msg
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg model msg appModel appMsg
|
||||
sandbox config =
|
||||
{ init = \c -> ( config.init, Cmd.none, Cmd.none )
|
||||
, update = \c msg model -> ( config.update msg model, Cmd.none, Cmd.none )
|
||||
, subscriptions = \c m -> Sub.none
|
||||
, view = \c model -> config.view model
|
||||
, toMsg = config.toMsg
|
||||
, toModel = config.toModel
|
||||
}
|
||||
|
||||
|
||||
element :
|
||||
{ init : flags -> ( model, Cmd msg )
|
||||
, update : msg -> model -> ( model, Cmd msg )
|
||||
, subscriptions : model -> Sub msg
|
||||
, view : model -> Html msg
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg model msg appModel appMsg
|
||||
element config =
|
||||
let
|
||||
appendCmd ( model, cmd ) =
|
||||
( model, cmd, Cmd.none )
|
||||
in
|
||||
{ init = \c -> config.init c.flags |> appendCmd
|
||||
, update = \c msg model -> config.update msg model |> appendCmd
|
||||
, subscriptions = \c model -> config.subscriptions model
|
||||
, view = \c model -> config.view model
|
||||
, toMsg = config.toMsg
|
||||
, toModel = config.toModel
|
||||
}
|
||||
|
||||
|
||||
page :
|
||||
{ init : Context flags route contextModel -> ( model, Cmd msg )
|
||||
, update : Context flags route contextModel -> msg -> model -> ( model, Cmd msg )
|
||||
, subscriptions : Context flags route contextModel -> model -> Sub msg
|
||||
, view : Context flags route contextModel -> model -> Html msg
|
||||
, toMsg : msg -> appMsg
|
||||
, toModel : model -> appModel
|
||||
}
|
||||
-> Page route flags contextModel contextMsg model msg appModel appMsg
|
||||
page config =
|
||||
let
|
||||
appendCmd ( model, cmd ) =
|
||||
( model, cmd, Cmd.none )
|
||||
in
|
||||
{ init = \c -> config.init c |> appendCmd
|
||||
, update = \c msg model -> config.update c msg model |> appendCmd
|
||||
, subscriptions = \c model -> config.subscriptions c model
|
||||
, view = \c model -> config.view c model
|
||||
, toMsg = config.toMsg
|
||||
, toModel = config.toModel
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- UTILS
|
||||
|
||||
|
||||
mapTruple :
|
||||
{ fromMsg : msg -> appMsg
|
||||
, fromModel : model -> appModel
|
||||
}
|
||||
-> ( model, Cmd msg, Cmd contextMsg )
|
||||
-> ( appModel, Cmd appMsg, Cmd contextMsg )
|
||||
mapTruple { fromModel, fromMsg } ( a, b, c ) =
|
||||
( fromModel a
|
||||
, Cmd.map fromMsg b
|
||||
, c
|
||||
)
|
Loading…
Reference in New Issue
Block a user