mirror of
https://github.com/ryan-haskell/elm-spa.git
synced 2024-09-11 12:35:44 +03:00
add advanced module for non-html apps
This commit is contained in:
parent
2cc0bbfaea
commit
581e47eb00
49
README.md
49
README.md
@ -8,11 +8,13 @@ When you create an app with the [elm/browser](https://package.elm-lang.org/packa
|
||||
|
||||
__elm-spa__ uses that design at the page-level, so you can quickly add new pages to your Elm application!
|
||||
|
||||
Make your page as simple as you need:
|
||||
✅ Automatically generate routes and pages
|
||||
|
||||
✅ Read and update global state across pages
|
||||
|
||||
## static pages
|
||||
|
||||
```elm
|
||||
module Pages.Home exposing (page)
|
||||
|
||||
-- can render a static page
|
||||
page : Page Flags Model Msg
|
||||
page =
|
||||
@ -21,9 +23,9 @@ page =
|
||||
}
|
||||
```
|
||||
|
||||
```elm
|
||||
module Pages.About exposing (page)
|
||||
## sandbox pages
|
||||
|
||||
```elm
|
||||
-- can keep track of page state
|
||||
page : Page Flags Model Msg
|
||||
page =
|
||||
@ -34,9 +36,9 @@ page =
|
||||
}
|
||||
```
|
||||
|
||||
```elm
|
||||
module Pages.Posts exposing (page)
|
||||
## element pages
|
||||
|
||||
```elm
|
||||
-- can perform side effects
|
||||
page : Page Flags Model Msg
|
||||
page =
|
||||
@ -48,9 +50,9 @@ page =
|
||||
}
|
||||
```
|
||||
|
||||
```elm
|
||||
module Pages.SignIn exposing (page)
|
||||
## component pages
|
||||
|
||||
```elm
|
||||
-- can read and update global state
|
||||
page : Page Flags Model Msg
|
||||
page =
|
||||
@ -62,7 +64,14 @@ page =
|
||||
}
|
||||
```
|
||||
|
||||
### putting your pages together is super easy!
|
||||
## easily put together pages!
|
||||
|
||||
The reason we return the same `Page` type is to make it super
|
||||
easy to write top-level `init`, `update`, `view`, and `susbcriptions` functions.
|
||||
|
||||
(And if you're using the [official cli tool](https://npmjs.org/elm-spa), this code will be automatically generated for you)
|
||||
|
||||
### `init`
|
||||
|
||||
```elm
|
||||
init : Route -> Global.Model -> ( Model, Cmd Msg, Cmd Global.Msg )
|
||||
@ -74,6 +83,8 @@ init route =
|
||||
Route.SignIn -> pages.signIn.init ()
|
||||
```
|
||||
|
||||
### `update`
|
||||
|
||||
```elm
|
||||
update : Msg -> Model -> Global.Model -> ( Model, Cmd Msg, Cmd Global.Msg )
|
||||
update bigMsg bigModel =
|
||||
@ -94,6 +105,8 @@ update bigMsg bigModel =
|
||||
always ( bigModel, Cmd.none, Cmd.none )
|
||||
```
|
||||
|
||||
### `view` + `subscriptions`
|
||||
|
||||
```elm
|
||||
-- handle view and subscriptions in one case expression!
|
||||
bundle : Model -> Global.Model -> { view : Document Msg, subscriptions : Sub Msg }
|
||||
@ -127,10 +140,22 @@ elm install ryannhg/elm-spa
|
||||
### rather see an example?
|
||||
|
||||
This repo comes with an example project that you can
|
||||
play around with. add in some pages and see how it works!
|
||||
play around with. Add in some pages and see how it works!
|
||||
|
||||
#### html example
|
||||
|
||||
```
|
||||
git clone https://github.com/ryannhg/elm-spa
|
||||
cd example
|
||||
cd elm-spa/examples/html
|
||||
npm start
|
||||
```
|
||||
|
||||
#### elm-ui example
|
||||
|
||||
```
|
||||
git clone https://github.com/ryannhg/elm-spa
|
||||
cd elm-spa/examples/elm-ui
|
||||
npm start
|
||||
```
|
||||
|
||||
The __elm-spa__ will be running at http://localhost:8000
|
||||
|
5
elm.json
5
elm.json
@ -3,9 +3,10 @@
|
||||
"name": "ryannhg/elm-spa",
|
||||
"summary": "a way to build single page apps with Elm",
|
||||
"license": "BSD-3-Clause",
|
||||
"version": "4.0.0",
|
||||
"version": "4.1.0",
|
||||
"exposed-modules": [
|
||||
"Spa"
|
||||
"Spa",
|
||||
"Spa.Advanced"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
|
45
src/Spa.elm
45
src/Spa.elm
@ -209,6 +209,7 @@ You can check out <https://elm-spa.dev> or join #elm-spa-users on the official E
|
||||
|
||||
import Browser exposing (Document)
|
||||
import Html
|
||||
import Spa.Advanced as Advanced
|
||||
|
||||
|
||||
|
||||
@ -242,12 +243,8 @@ static :
|
||||
{ view : Document msg
|
||||
}
|
||||
-> Page flags () msg globalModel globalMsg
|
||||
static options =
|
||||
{ init = \_ _ -> ( (), Cmd.none, Cmd.none )
|
||||
, update = \_ _ model -> ( model, Cmd.none, Cmd.none )
|
||||
, view = \_ _ -> options.view
|
||||
, subscriptions = \_ _ -> Sub.none
|
||||
}
|
||||
static =
|
||||
Advanced.static
|
||||
|
||||
|
||||
{-|
|
||||
@ -269,12 +266,8 @@ sandbox :
|
||||
, view : model -> Document msg
|
||||
}
|
||||
-> Page flags model msg globalModel globalMsg
|
||||
sandbox options =
|
||||
{ init = \_ _ -> ( options.init, Cmd.none, Cmd.none )
|
||||
, update = \_ msg model -> ( options.update msg model, Cmd.none, Cmd.none )
|
||||
, view = always options.view
|
||||
, subscriptions = \_ _ -> Sub.none
|
||||
}
|
||||
sandbox =
|
||||
Advanced.sandbox
|
||||
|
||||
|
||||
{-|
|
||||
@ -296,12 +289,8 @@ element :
|
||||
, subscriptions : model -> Sub msg
|
||||
}
|
||||
-> Page flags model msg globalModel globalMsg
|
||||
element page =
|
||||
{ init = \_ flags -> page.init flags |> (\( model, cmd ) -> ( model, cmd, Cmd.none ))
|
||||
, update = \_ msg model -> page.update msg model |> (\( model_, cmd ) -> ( model_, cmd, Cmd.none ))
|
||||
, subscriptions = always page.subscriptions
|
||||
, view = always page.view
|
||||
}
|
||||
element =
|
||||
Advanced.element
|
||||
|
||||
|
||||
{-|
|
||||
@ -327,7 +316,7 @@ component :
|
||||
}
|
||||
-> Page flags model msg globalModel globalMsg
|
||||
component =
|
||||
identity
|
||||
Advanced.component
|
||||
|
||||
|
||||
{-| For each page we export from our `Pages.*` modules, we should call the `upgrade` function with the corresponding `Model` and `Msg` variants, like this:
|
||||
@ -349,19 +338,13 @@ upgrade :
|
||||
, update : pageMsg -> pageModel -> globalModel -> ( model, Cmd msg, Cmd globalMsg )
|
||||
, bundle : pageModel -> globalModel -> Bundle msg
|
||||
}
|
||||
upgrade toModel toMsg page =
|
||||
{ init =
|
||||
\flags global ->
|
||||
page.init global flags |> (\( model, cmd, globalCmd ) -> ( toModel model, Cmd.map toMsg cmd, globalCmd ))
|
||||
, update =
|
||||
\msg model global ->
|
||||
page.update global msg model |> (\( model_, cmd, globalCmd ) -> ( toModel model_, Cmd.map toMsg cmd, globalCmd ))
|
||||
, bundle =
|
||||
\model global ->
|
||||
{ view = page.view global model |> (\doc -> { title = doc.title, body = List.map (Html.map toMsg) doc.body })
|
||||
, subscriptions = page.subscriptions global model |> Sub.map toMsg
|
||||
upgrade =
|
||||
Advanced.upgrade
|
||||
(\toMsg doc ->
|
||||
{ title = doc.title
|
||||
, body = List.map (Html.map toMsg) doc.body
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-|
|
||||
|
293
src/Spa/Advanced.elm
Normal file
293
src/Spa/Advanced.elm
Normal file
@ -0,0 +1,293 @@
|
||||
module Spa.Advanced exposing
|
||||
( Page
|
||||
, static
|
||||
, sandbox
|
||||
, element
|
||||
, component
|
||||
, upgrade
|
||||
, Bundle
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
## prefer elm-ui?
|
||||
|
||||
If you'd rather use something like [elm-ui](https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/)
|
||||
or have your page's `view` functions return something besides `Html`, this module
|
||||
extends the `Spa.Page` module's API with one additional parameter.
|
||||
|
||||
@docs Page
|
||||
|
||||
|
||||
## avoid doing this
|
||||
|
||||
Instead of having all the pages in your app specify all 6 parameters
|
||||
_every time_:
|
||||
|
||||
import Browser exposing (Document)
|
||||
import Global
|
||||
import Spa
|
||||
|
||||
page : Spa.Page Flags Model Msg Global.Model Global.Msg (Document Msg)
|
||||
page =
|
||||
Spa.static
|
||||
{ view = view
|
||||
}
|
||||
|
||||
|
||||
## try this instead!
|
||||
|
||||
It's recommended to create a single `src/Page.elm` file for your project, just
|
||||
[like the one created by `elm-spa init`](https://github.com/ryannhg/elm-spa/blob/master/cli/projects/new/src/Page.elm).
|
||||
|
||||
The `Page` module exposes type aliases and functions that know which `Global.Model` and `Global.Msg`
|
||||
to use, which allow the compiler to provide better error messages.
|
||||
|
||||
Additionally, it make your type annotations way easier to read!
|
||||
|
||||
import Page exposing (Document, Page)
|
||||
|
||||
page : Page Flags Model Msg
|
||||
page =
|
||||
Page.static
|
||||
{ view = view
|
||||
}
|
||||
|
||||
(If you're doing things correctly you should only see `import Spa` or
|
||||
`import Spa.Advanced` once in your entire project!)
|
||||
|
||||
|
||||
## compatible with the cli tool!
|
||||
|
||||
Using the technique describe above means the [cli tool](https://npmjs.org/elm-spa)
|
||||
will be able to work
|
||||
|
||||
These links are full `src/Page.elm` implementations you can drop
|
||||
into your app:
|
||||
|
||||
- [Using Html (with Browser.Document)](https://gist.github.com/ryannhg/914f45a83a980d7c765d62a093ad6f38)
|
||||
- [Using Elm UI](https://gist.github.com/ryannhg/c501f9a31727c4917fccd669ffbd9ef3)
|
||||
|
||||
|
||||
# static pages
|
||||
|
||||
@docs static
|
||||
|
||||
|
||||
# sandbox pages
|
||||
|
||||
@docs sandbox
|
||||
|
||||
|
||||
# element pages
|
||||
|
||||
@docs element
|
||||
|
||||
|
||||
# component pages
|
||||
|
||||
@docs component
|
||||
|
||||
|
||||
## upgrading pages
|
||||
|
||||
The `Page` module discussed above should also export
|
||||
an `upgrade` function. This means providing an extra function
|
||||
to map one view to another.
|
||||
|
||||
|
||||
### an example with elm/browser
|
||||
|
||||
import Browser
|
||||
import Html
|
||||
|
||||
type alias Document msg =
|
||||
Browser.Document msg
|
||||
|
||||
upgrade =
|
||||
Spa.Advanced.upgrade
|
||||
(\fn doc ->
|
||||
{ title = doc.title
|
||||
, body = List.map (Html.map fn) doc.body
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
### an example with mdgriffith/elm-ui
|
||||
|
||||
import Element exposing (Element)
|
||||
|
||||
type alias Document msg =
|
||||
{ title : String
|
||||
, body : List (Element msg)
|
||||
}
|
||||
|
||||
upgrade =
|
||||
Spa.Advanced.upgrade
|
||||
(\fn doc ->
|
||||
{ title = doc.title
|
||||
, body = List.map (Element.map fn) doc.body
|
||||
}
|
||||
)
|
||||
|
||||
@docs upgrade
|
||||
|
||||
@docs Bundle
|
||||
|
||||
-}
|
||||
|
||||
-- PAGE
|
||||
|
||||
|
||||
{-| Just like [Spa.Page](https://package.elm-lang.org/packages/ryannhg/elm-spa/latest/Spa#Page),
|
||||
but the `view` function returns `view_msg` instead of enforcing the use of
|
||||
[Browser.Document msg](https://package.elm-lang.org/packages/elm/browser/latest/Browser#Document)
|
||||
-}
|
||||
type alias Page flags model msg globalModel globalMsg view_msg =
|
||||
{ init : globalModel -> flags -> ( model, Cmd msg, Cmd globalMsg )
|
||||
, update : globalModel -> msg -> model -> ( model, Cmd msg, Cmd globalMsg )
|
||||
, view : globalModel -> model -> view_msg
|
||||
, subscriptions : globalModel -> model -> Sub msg
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
import Page exposing (Page)
|
||||
|
||||
page : Page Flags Model Msg
|
||||
page =
|
||||
Page.static
|
||||
{ view = view
|
||||
}
|
||||
|
||||
-}
|
||||
static :
|
||||
{ view : view_msg
|
||||
}
|
||||
-> Page flags () msg globalModel globalMsg view_msg
|
||||
static options =
|
||||
{ init = \_ _ -> ( (), Cmd.none, Cmd.none )
|
||||
, update = \_ _ model -> ( model, Cmd.none, Cmd.none )
|
||||
, view = \_ _ -> options.view
|
||||
, subscriptions = \_ _ -> Sub.none
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
import Page exposing (Page)
|
||||
|
||||
page : Page Flags Model Msg
|
||||
page =
|
||||
Page.sandbox
|
||||
{ init = init
|
||||
, update = update
|
||||
, view = view
|
||||
}
|
||||
|
||||
-}
|
||||
sandbox :
|
||||
{ init : model
|
||||
, update : msg -> model -> model
|
||||
, view : model -> view_msg
|
||||
}
|
||||
-> Page flags model msg globalModel globalMsg view_msg
|
||||
sandbox options =
|
||||
{ init = \_ _ -> ( options.init, Cmd.none, Cmd.none )
|
||||
, update = \_ msg model -> ( options.update msg model, Cmd.none, Cmd.none )
|
||||
, view = always options.view
|
||||
, subscriptions = \_ _ -> Sub.none
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
import Page exposing (Page)
|
||||
|
||||
page : Page Flags Model Msg
|
||||
page =
|
||||
Page.element
|
||||
{ init = init
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
, view = view
|
||||
}
|
||||
|
||||
-}
|
||||
element :
|
||||
{ init : flags -> ( model, Cmd msg )
|
||||
, update : msg -> model -> ( model, Cmd msg )
|
||||
, view : model -> view_msg
|
||||
, subscriptions : model -> Sub msg
|
||||
}
|
||||
-> Page flags model msg globalModel globalMsg view_msg
|
||||
element page =
|
||||
{ init = \_ flags -> page.init flags |> (\( model, cmd ) -> ( model, cmd, Cmd.none ))
|
||||
, update = \_ msg model -> page.update msg model |> (\( model_, cmd ) -> ( model_, cmd, Cmd.none ))
|
||||
, subscriptions = always page.subscriptions
|
||||
, view = always page.view
|
||||
}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
import Page exposing (Page)
|
||||
|
||||
page : Page Flags Model Msg
|
||||
page =
|
||||
Page.component
|
||||
{ init = init
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
, view = view
|
||||
}
|
||||
|
||||
-}
|
||||
component :
|
||||
{ init : globalModel -> flags -> ( model, Cmd msg, Cmd globalMsg )
|
||||
, update : globalModel -> msg -> model -> ( model, Cmd msg, Cmd globalMsg )
|
||||
, view : globalModel -> model -> view_msg
|
||||
, subscriptions : globalModel -> model -> Sub msg
|
||||
}
|
||||
-> Page flags model msg globalModel globalMsg view_msg
|
||||
component =
|
||||
identity
|
||||
|
||||
|
||||
{-| Same as [Spa.upgrade](https://package.elm-lang.org/packages/ryannhg/elm-spa/latest/Spa#upgrade), but needs an extra map function as the first argument
|
||||
to upgrade one view to another!
|
||||
-}
|
||||
upgrade :
|
||||
((pageMsg -> msg) -> view_pageMsg -> view_msg)
|
||||
-> (pageModel -> model)
|
||||
-> (pageMsg -> msg)
|
||||
-> Page pageFlags pageModel pageMsg globalModel globalMsg view_pageMsg
|
||||
->
|
||||
{ init : pageFlags -> globalModel -> ( model, Cmd msg, Cmd globalMsg )
|
||||
, update : pageMsg -> pageModel -> globalModel -> ( model, Cmd msg, Cmd globalMsg )
|
||||
, bundle : pageModel -> globalModel -> Bundle msg view_msg
|
||||
}
|
||||
upgrade viewMap toModel toMsg page =
|
||||
{ init =
|
||||
\flags global ->
|
||||
page.init global flags |> (\( model, cmd, globalCmd ) -> ( toModel model, Cmd.map toMsg cmd, globalCmd ))
|
||||
, update =
|
||||
\msg model global ->
|
||||
page.update global msg model |> (\( model_, cmd, globalCmd ) -> ( toModel model_, Cmd.map toMsg cmd, globalCmd ))
|
||||
, bundle =
|
||||
\model global ->
|
||||
{ view = page.view global model |> viewMap toMsg
|
||||
, subscriptions = page.subscriptions global model |> Sub.map toMsg
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{-| Bundle behaves the same as [Spa.Bundle](https://package.elm-lang.org/packages/ryannhg/elm-spa/latest/Spa#Bundle),
|
||||
but supports more than just `Browser.Document msg` for the view's return type!
|
||||
-}
|
||||
type alias Bundle msg view_msg =
|
||||
{ view : view_msg
|
||||
, subscriptions : Sub msg
|
||||
}
|
Loading…
Reference in New Issue
Block a user