mirror of
https://github.com/ryan-haskell/elm-spa.git
synced 2024-11-25 08:04:06 +03:00
hey man the examples are up
This commit is contained in:
parent
f757b10371
commit
8553d14b85
@ -1,31 +1,38 @@
|
||||
# Examples
|
||||
|
||||
Prefer to learn by example? Wonderful! The source code for all of the examples on this site can be found in the Github repo's [examples](https://github.com/ryannhg/elm-spa/tree/main/examples) folder.
|
||||
Prefer to learn by example? Wonderful! The source code for all of the examples on this site can be found in the GitHub repo's [examples](https://github.com/ryannhg/elm-spa/tree/main/examples) folder.
|
||||
|
||||
### Hello, world!
|
||||
|
||||
Get an introduction to the framework with a simple app.
|
||||
|
||||
[![Hello, world!](/content/images/01-hello-world.png)](/examples/01-hello-world)
|
||||
[![Example 1 screenshot](/content/images/01-hello-world.png)](/examples/01-hello-world)
|
||||
|
||||
### Pages
|
||||
|
||||
Learn how pages and URL routing work together.
|
||||
|
||||
[![Hello, world!](/content/images/02-pages.png)](/examples/02-pages)
|
||||
[![Example 2 screenshot](/content/images/02-pages.png)](/examples/02-pages)
|
||||
|
||||
### Local storage
|
||||
|
||||
Use ports and local storage to persist data on refresh.
|
||||
|
||||
[![Hello, world!](/content/images/03-storage.png)](/examples/03-storage)
|
||||
[![Example 3 screenshot](/content/images/03-storage.png)](/examples/03-storage)
|
||||
|
||||
### User authentication
|
||||
|
||||
Explore the elm-spa's user authentication API.
|
||||
|
||||
[![Hello, world!](/content/images/04-authentication.png)](/examples/04-authentication)
|
||||
[![Example 4 screenshot](/content/images/04-authentication.png)](/examples/04-authentication)
|
||||
|
||||
## Realworld example
|
||||
|
||||
Implements the [Realworld app project](), inspired by Richard Feldman's "elm-spa-example" project.
|
||||
|
||||
[![Realworld app screenshot](/content/images/realworld.png)](https://realworld.elm-spa.dev)
|
||||
|
||||
Source code: [GitHub](https://github.com/ryannhg/elm-spa-realworld)
|
||||
|
||||
## More examples
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Hello, world!
|
||||
|
||||
__Source code__: [GitHub](https://github.com/ryannhg/elm-spa/tree/main/examples/01-hello-world)
|
||||
|
||||
Welcome to __elm-spa__! This guide is a breakdown of the simplest project you can make: the "Hello, world!" example.
|
||||
|
||||
### Installation
|
||||
@ -134,3 +136,7 @@ elm-spa build
|
||||
|
||||
This command will also minify your `/dist/elm.js` file so it's production ready.
|
||||
|
||||
|
||||
---
|
||||
|
||||
__Next up:__ [Pages](./02-pages)
|
@ -1,8 +1,9 @@
|
||||
# Pages & routing
|
||||
|
||||
__Source code__: [GitHub](https://github.com/ryannhg/elm-spa/tree/main/examples/01-pages)
|
||||
|
||||
This next guide will show you how pages, routing, and the `elm-spa add` command work together to automatically handle URLs in your __elm-spa__ application.
|
||||
|
||||
> You can see the source code in the [examples](https://github.com/ryannhg/elm-spa/tree/next/examples/02-pages) folder on GitHub.
|
||||
|
||||
### The setup
|
||||
|
||||
@ -202,3 +203,7 @@ After creating `style.css`, we can import the file in our `public/index.html` en
|
||||
|
||||
Using the `<link>` tag as shown above (with the leading slash!) imports our CSS file. All files in the `public` folder are available at the root of our web application. That means a file stored at `public/images/dog.png` would be at `http://localhost:1234/images/dog`, without including `public` in the URL at all.
|
||||
|
||||
|
||||
---
|
||||
|
||||
__Next up:__ [Storage](./03-storage)
|
||||
|
@ -1,3 +1,394 @@
|
||||
# Storage
|
||||
|
||||
> Coming soon!
|
||||
__Source code__: [GitHub](https://github.com/ryannhg/elm-spa/tree/main/examples/03-storage)
|
||||
|
||||
Let's start by creating a new project with the __elm-spa__ CLI:
|
||||
|
||||
```terminal
|
||||
elm-spa new
|
||||
```
|
||||
|
||||
## Creating a stateful page
|
||||
|
||||
Let's create a simple interactive app, based on the official Elm [counter example](https://elm-lang.org/examples/buttons). The `elm-spa add` command will make this a breeze:
|
||||
|
||||
```terminal
|
||||
elm-spa add / sandbox
|
||||
```
|
||||
|
||||
This will stub out the `init`, `update`, and `view` function for us, and wire them together with `Page.static` like this:
|
||||
|
||||
```elm
|
||||
-- src/Pages/Home_.elm
|
||||
|
||||
page : Shared.Model -> Request -> Page.With Model Msg
|
||||
page =
|
||||
Page.static
|
||||
{ init = init
|
||||
, update = update
|
||||
, view = view
|
||||
}
|
||||
```
|
||||
|
||||
Let's add in the implementation from the counter example to get a working app!
|
||||
|
||||
|
||||
### init
|
||||
|
||||
```elm
|
||||
-- src/Pages/Home_.elm
|
||||
|
||||
type alias Model =
|
||||
{ counter : Int
|
||||
}
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ counter = 0
|
||||
}
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
```elm
|
||||
-- src/Pages/Home_.elm
|
||||
|
||||
type Msg = Increment | Decrement
|
||||
|
||||
update : Msg -> Model -> Model
|
||||
update msg model =
|
||||
case msg of
|
||||
Increment ->
|
||||
{ model | counter = model.counter + 1 }
|
||||
|
||||
Decrement ->
|
||||
{ model | counter = model.counter - 1 }
|
||||
```
|
||||
|
||||
### view
|
||||
|
||||
```elm
|
||||
-- src/Pages/Home_.elm
|
||||
|
||||
view : Model -> View Msg
|
||||
view model =
|
||||
{ title = "Homepage"
|
||||
, body =
|
||||
[ Html.h1 [] [ Html.text "Local storage" ]
|
||||
, Html.button [ Html.Events.onClick Increment ] [ Html.text "+" ]
|
||||
, Html.p [] [ Html.text ("Count: " ++ String.fromInt model.counter) ]
|
||||
, Html.button [ Html.Events.onClick Decrement ] [ Html.text "-" ]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
After these functions are in place, we can spin up our server with the __elm-spa__ CLI:
|
||||
|
||||
```terminal
|
||||
elm-spa server
|
||||
```
|
||||
|
||||
And this is what we should see at [http://localhost:1234](http://localhost:1234):
|
||||
|
||||
![counter app](/content/images/03-storage.png)
|
||||
|
||||
### Playing with the counter
|
||||
|
||||
As we click the "+" and "-" buttons, the counter value should be working great. When we __refresh__ the page, the counter value is 0 again.
|
||||
|
||||
Let's use local storage to keep the counter value around!
|
||||
|
||||
## The JS side
|
||||
|
||||
To do this, we'll be using [flags and ports](https://guide.elm-lang.org/interop/ports.html), a typesafe way to work with JavaScript without causing runtime errors in our Elm application!
|
||||
|
||||
Let's edit `public/index.html` as a starting point:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<script src="/dist/elm.js"></script>
|
||||
|
||||
<!-- EDIT THIS LINE -->
|
||||
<script src="/main.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Here we replace the inline `Elm.Main.init()` script generated by the `elm-spa new` command with a reference to a new file we'll create in `public/main.js`
|
||||
|
||||
```js
|
||||
// public/main.js
|
||||
|
||||
const app = Elm.Main.init()
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
At this point, nothing has changed yet, but we now have access to `app`– which will allow us to interact with our Elm app from the JS file!
|
||||
|
||||
Let's add in some ports like this:
|
||||
|
||||
```js
|
||||
// public/main.js
|
||||
|
||||
const app = Elm.Main.init({
|
||||
flags: JSON.parse(localStorage.getItem('storage'))
|
||||
})
|
||||
|
||||
app.ports.save.subscribe(storage => {
|
||||
localStorage.setItem('storage', JSON.stringify(storage))
|
||||
app.ports.load.send(storage)
|
||||
})
|
||||
```
|
||||
|
||||
This JS code is doing a few things:
|
||||
|
||||
1. __When our Elm app starts up,__ we pass in the current value of `localStorage` via flags. Initially, this will pass in `null`, because no data has been stored yet.
|
||||
|
||||
2. We subscribe to the `save` port for events __from Elm__, which we'll wire up on the Elm side shortly.
|
||||
|
||||
3. When Elm sends a `save` event, we'll store the data in localStorage (making it ready for the next time the app starts up!) as well as send a message back to Elm via the `load` port.
|
||||
|
||||
## The Elm side
|
||||
|
||||
None of this code is working yet, because we need to define these `save` and `load` ports on the Elm side too!
|
||||
|
||||
Let's create a new file at `src/Storage.elm` that defines the ports referenced on the JS side:
|
||||
|
||||
```elm
|
||||
port module Storage exposing (..)
|
||||
|
||||
import Json.Decode as Json
|
||||
|
||||
port save : Json.Value -> Cmd msg
|
||||
|
||||
port load : (Json.Value -> msg) -> Sub msg
|
||||
```
|
||||
|
||||
Above, we've created a `port module` that defines our `save` and `load` ports. Next, we'll describe the data we want to store, as well as how to convert it to and from JSON:
|
||||
|
||||
```elm
|
||||
port module Storage exposing (..)
|
||||
|
||||
import Json.Encode as Encode
|
||||
|
||||
-- ... port definitions from before ...
|
||||
|
||||
type alias Storage =
|
||||
{ counter : Int
|
||||
}
|
||||
|
||||
|
||||
-- Converting to JSON
|
||||
|
||||
toJson : Storage -> Json.Value
|
||||
toJson storage =
|
||||
Encode.object
|
||||
[ ( "counter", Encode.int storage.counter )
|
||||
]
|
||||
|
||||
|
||||
-- Converting from JSON
|
||||
|
||||
fromJson : Json.Value -> Storage
|
||||
fromJson value =
|
||||
value
|
||||
|> Json.decodeValue decoder
|
||||
|> Result.withDefault initial
|
||||
|
||||
decoder : Json.Decoder Storage
|
||||
decoder =
|
||||
Json.map Storage
|
||||
(Json.field "counter" Json.int)
|
||||
|
||||
initial : Storage
|
||||
initial =
|
||||
{ counter = 0
|
||||
}
|
||||
```
|
||||
|
||||
If this decoder stuff is new to you, please check out the [JSON section of the Elm guide](https://guide.elm-lang.org/effects/json.html). It will lay a solid foundation for understanding decoders and encode functions!
|
||||
|
||||
### Sending data to JS
|
||||
|
||||
For this example, we're going to define `increment` and `decrement` as side-effects because they change the state of the world. We'll be using the `save` port to send these events to JS:
|
||||
|
||||
```elm
|
||||
-- src/Storage.elm
|
||||
|
||||
increment : Storage -> Cmd msg
|
||||
increment storage =
|
||||
{ storage | counter = storage.counter + 1 }
|
||||
|> toJson
|
||||
|> save
|
||||
|
||||
decrement : Storage -> Cmd msg
|
||||
decrement storage =
|
||||
{ storage | counter = storage.counter - 1 }
|
||||
|> toJson
|
||||
|> save
|
||||
```
|
||||
|
||||
This should look pretty similar to how our homepage handled the `Increment` and `Decrement` messages, but this time we use `toJson` and `save` to send an event for JS to handle.
|
||||
|
||||
( As a last step, we'll revisit `Home_.elm` and swap out the old behavior with the new )
|
||||
|
||||
### Listening for data from JS
|
||||
|
||||
We're going to add one final function to `Storage.elm` that will allow us to subscribe to events from the `load` port, that use's our `fromJson` function to safely parse the message we get back:
|
||||
|
||||
```elm
|
||||
onChange : (Storage -> msg) -> Sub msg
|
||||
onChange fromStorage =
|
||||
load (\json -> fromJson json |> fromStorage)
|
||||
```
|
||||
|
||||
Here, the `onChange` function will allow the outside world to handle the `load` event without having to deal with raw JSON values by hand.
|
||||
|
||||
That's it for this file- now we're ready to use our `Storage` module in our app!
|
||||
|
||||
### Wiring up the shared state
|
||||
|
||||
Let's eject `Shared.elm` by moving it from `.elm-spa/defaults` into our `src` folder. This will allow us to make local changes to it, as explained in the [shared state section](/guides/05-shared-state) of the guide.
|
||||
|
||||
Our first step is to add `Storage` to our `Shared.Model`, so we can access `storage` from _any_ page in our application:
|
||||
|
||||
```elm
|
||||
-- src/Shared.elm
|
||||
|
||||
import Storage
|
||||
|
||||
type alias Model =
|
||||
{ storage : Storage
|
||||
}
|
||||
```
|
||||
|
||||
The `Shared.init` function is the __only place__ we have access to `Flags`, which is how JS passed in our initial value earlier. We can use `Storage.fromJson` to convert that raw JSON into our nice `Storage` type.
|
||||
|
||||
```elm
|
||||
-- src/Shared.elm
|
||||
|
||||
init : Request -> Flags -> ( Model, Cmd Msg )
|
||||
init _ flags =
|
||||
( { storage = Storage.fromJson flags }
|
||||
, Cmd.none
|
||||
)
|
||||
```
|
||||
|
||||
Now let's listen for those `load` events from JS, so we can update the `Shared.Model` as soon as we get them. This code will use the `Storage.onChange` function we made to send a `Shared.Msg` to our `Shared.update` function:
|
||||
|
||||
```elm
|
||||
-- src/Shared.elm
|
||||
|
||||
subscriptions : Request -> Model -> Sub Msg
|
||||
subscriptions _ _ =
|
||||
Storage.onChange StorageUpdated
|
||||
|
||||
|
||||
type Msg
|
||||
= StorageUpdated Storage
|
||||
|
||||
update : Request -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update _ msg model =
|
||||
case msg of
|
||||
StorageUpdated storage ->
|
||||
( { model | storage = storage }
|
||||
, Cmd.none
|
||||
)
|
||||
```
|
||||
|
||||
That's all for `src/Shared.elm`. The last step is to upgrade our homepage to send side-effects instead of changing the data locally.
|
||||
|
||||
### Upgrading Home_.elm
|
||||
|
||||
To gain access to `Cmd msg`, we'll start by using `Page.element` instead of `Page.static`. The signature of our `init` and `update` functions will need to change to handle the new capabilities:
|
||||
|
||||
Our `Model` no longer needs to track the state of the application. This means the `Home_.init` function won't be doing much at all:
|
||||
|
||||
```elm
|
||||
-- src/Pages/Home_.elm
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
( {}, Cmd.none )
|
||||
```
|
||||
|
||||
This time around, the `update` function will need access to the current `Storage` value and use `Storage.increment` and `Storage.decrement` to send commands to the JS side.
|
||||
|
||||
|
||||
```elm
|
||||
-- src/Pages/Home_.elm
|
||||
|
||||
type Msg
|
||||
= Increment
|
||||
| Decrement
|
||||
|
||||
update : Storage -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update storage msg model =
|
||||
case msg of
|
||||
Increment ->
|
||||
( model
|
||||
, Storage.increment storage
|
||||
)
|
||||
|
||||
Decrement ->
|
||||
( model
|
||||
, Storage.decrement storage
|
||||
)
|
||||
```
|
||||
|
||||
When the `load` event comes in from JS, it triggers our `Storage.onChange` subscription. This updates the `storage` for us, meaning the `storage.counter` we get in our `view` will be the latest counter value.
|
||||
|
||||
```elm
|
||||
-- src/Pages/Home_.elm
|
||||
|
||||
view : Storage -> Model -> View Msg
|
||||
view storage _ =
|
||||
{ title = "Homepage"
|
||||
, body =
|
||||
[ Html.h1 [] [ Html.text "Local storage" ]
|
||||
, Html.button [ Html.Events.onClick Increment ] [ Html.text "+" ]
|
||||
, Html.p [] [ Html.text ("Count: " ++ String.fromInt storage.counter) ]
|
||||
, Html.button [ Html.Events.onClick Decrement ] [ Html.text "-" ]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
We can use `Page.element` to wire all these things up, and even pass `Storage` into our `view` and `update` functions, which depend on the current value to do their thing:
|
||||
|
||||
|
||||
```elm
|
||||
-- src/Pages/Home_.elm
|
||||
|
||||
page : Shared.Model -> Request -> Page.With Model Msg
|
||||
page shared _ =
|
||||
Page.element
|
||||
{ init = init
|
||||
, update = update shared.storage
|
||||
, view = view shared.storage
|
||||
, subscriptions = \_ -> Sub.none
|
||||
}
|
||||
```
|
||||
|
||||
> Here, I've stubbed out `subscriptions` with an inline function, we won't be needing it, because Shared.subscriptions listens to `Storage.onChange` for us.
|
||||
|
||||
|
||||
#### Hooray!
|
||||
|
||||
In the browser, we now have a working counter app that persists on refresh. Even if you close the browser and open it up again, you'll see your previous counter value on the screen.
|
||||
|
||||
As a reminder, all the source code for this example is available on [GitHub](https://github.com/ryannhg/elm-spa/tree/main/examples/03-storage)
|
||||
|
||||
---
|
||||
|
||||
__Next up:__ [User Authentication](./04-authentication)
|
@ -1,5 +1,7 @@
|
||||
# User authentication
|
||||
|
||||
__Source code__: [GitHub](https://github.com/ryannhg/elm-spa/tree/main/examples/04-authentication)
|
||||
|
||||
In a real world application, it's common to have the notion of a signed-in users. When it comes to routing, it's often useful to only allow signed-in users to visit specific pages.
|
||||
|
||||
It would be wonderful if we could define logic in _one place_ that guarantees only signed-in users could view those pages:
|
||||
@ -380,6 +382,30 @@ view user model =
|
||||
Now everything is working! Visiting the `/sign-in` page and clicking "Sign In" signs in the user and redirects to the homepage. Clicking "Sign out" on the homepage signs out the user, and our `Auth.elm` logic automatically redirects to the `SignIn` page.
|
||||
|
||||
|
||||
#### But wait...
|
||||
## Persisting the user
|
||||
|
||||
When we refresh the page, the user is signed out... how can we keep them signed in after refresh? Sounds like a job for [local storage](/examples/local-storage)!
|
||||
When we refresh the page, the user is signed out... how can we keep them signed in after refresh? Let's tweak the `Storage.elm` file we made in the [last example](./03-storage):
|
||||
|
||||
```elm
|
||||
-- src/Storage.elm
|
||||
|
||||
type alias Storage =
|
||||
{ user : Maybe User
|
||||
}
|
||||
```
|
||||
|
||||
If we store this on the `Shared.Model` we can ensure the user is still signed in after they refresh their browser, or visit the app later.
|
||||
|
||||
```elm
|
||||
-- src/Shared.elm
|
||||
|
||||
type alias Model =
|
||||
{ storage : Storage
|
||||
}
|
||||
```
|
||||
|
||||
For more explanation of how this works, check out the [Storage example](./03-storage) in the last section, it will give a better basic understanding of how this mechanism works!
|
||||
|
||||
---
|
||||
|
||||
__Next up:__ [More examples](/examples#more-examples)
|
BIN
docs/public/content/images/realworld.png
Normal file
BIN
docs/public/content/images/realworld.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 379 KiB |
@ -2,7 +2,7 @@ const app = Elm.Main.init({
|
||||
flags: JSON.parse(localStorage.getItem('storage'))
|
||||
})
|
||||
|
||||
app.ports.save_.subscribe(storage => {
|
||||
app.ports.save.subscribe(storage => {
|
||||
localStorage.setItem('storage', JSON.stringify(storage))
|
||||
app.ports.load_.send(storage)
|
||||
app.ports.load.send(storage)
|
||||
})
|
@ -1,18 +1,16 @@
|
||||
module Pages.Home_ exposing (Model, Msg, init, page, update, view)
|
||||
|
||||
import Gen.Params.Home_ exposing (Params)
|
||||
import Html exposing (Html)
|
||||
import Html
|
||||
import Html.Events
|
||||
import Page
|
||||
import Ports
|
||||
import Request
|
||||
import Request exposing (Request)
|
||||
import Shared
|
||||
import Storage exposing (Storage)
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
page : Shared.Model -> Request.With Params -> Page.With Model Msg
|
||||
page shared req =
|
||||
page : Shared.Model -> Request -> Page.With Model Msg
|
||||
page shared _ =
|
||||
Page.element
|
||||
{ init = init
|
||||
, update = update shared.storage
|
||||
@ -48,12 +46,12 @@ update storage msg model =
|
||||
case msg of
|
||||
Increment ->
|
||||
( model
|
||||
, Ports.save (Storage.increment storage)
|
||||
, Storage.increment storage
|
||||
)
|
||||
|
||||
Decrement ->
|
||||
( model
|
||||
, Ports.save (Storage.decrement storage)
|
||||
, Storage.decrement storage
|
||||
)
|
||||
|
||||
|
||||
@ -62,7 +60,7 @@ update storage msg model =
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
@ -71,7 +69,7 @@ subscriptions model =
|
||||
|
||||
|
||||
view : Storage -> Model -> View Msg
|
||||
view storage model =
|
||||
view storage _ =
|
||||
{ title = "Homepage"
|
||||
, body =
|
||||
[ Html.h1 [] [ Html.text "Local storage" ]
|
||||
|
@ -1,20 +0,0 @@
|
||||
port module Ports exposing (load, save)
|
||||
|
||||
import Json.Decode as Json
|
||||
import Storage exposing (Storage)
|
||||
|
||||
|
||||
save : Storage -> Cmd msg
|
||||
save =
|
||||
Storage.save >> save_
|
||||
|
||||
|
||||
load : (Storage -> msg) -> Sub msg
|
||||
load fromStorage =
|
||||
load_ (\json -> Storage.load json |> fromStorage)
|
||||
|
||||
|
||||
port save_ : Json.Value -> Cmd msg
|
||||
|
||||
|
||||
port load_ : (Json.Value -> msg) -> Sub msg
|
@ -8,7 +8,6 @@ module Shared exposing
|
||||
)
|
||||
|
||||
import Json.Decode as Json
|
||||
import Ports
|
||||
import Request exposing (Request)
|
||||
import Storage exposing (Storage)
|
||||
|
||||
@ -24,7 +23,7 @@ type alias Model =
|
||||
|
||||
init : Request -> Flags -> ( Model, Cmd Msg )
|
||||
init _ flags =
|
||||
( { storage = Storage.load flags }
|
||||
( { storage = Storage.fromJson flags }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
@ -37,9 +36,11 @@ update : Request -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update _ msg model =
|
||||
case msg of
|
||||
StorageUpdated storage ->
|
||||
( { model | storage = storage }, Cmd.none )
|
||||
( { model | storage = storage }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
subscriptions : Request -> Model -> Sub Msg
|
||||
subscriptions _ _ =
|
||||
Ports.load StorageUpdated
|
||||
Storage.onChange StorageUpdated
|
||||
|
@ -1,11 +1,11 @@
|
||||
module Storage exposing
|
||||
( Storage, save, load
|
||||
port module Storage exposing
|
||||
( Storage, fromJson, onChange
|
||||
, increment, decrement
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Storage, save, load
|
||||
@docs Storage, fromJson, onChange
|
||||
@docs increment, decrement
|
||||
|
||||
-}
|
||||
@ -14,13 +14,42 @@ import Json.Decode as Json
|
||||
import Json.Encode as Encode
|
||||
|
||||
|
||||
|
||||
-- PORTS
|
||||
|
||||
|
||||
port save : Json.Value -> Cmd msg
|
||||
|
||||
|
||||
port load : (Json.Value -> msg) -> Sub msg
|
||||
|
||||
|
||||
|
||||
-- STORAGE
|
||||
|
||||
|
||||
type alias Storage =
|
||||
{ counter : Int
|
||||
}
|
||||
|
||||
|
||||
load : Json.Value -> Storage
|
||||
load json =
|
||||
|
||||
-- Converting to JSON
|
||||
|
||||
|
||||
toJson : Storage -> Json.Value
|
||||
toJson storage =
|
||||
Encode.object
|
||||
[ ( "counter", Encode.int storage.counter )
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- Converting from JSON
|
||||
|
||||
|
||||
fromJson : Json.Value -> Storage
|
||||
fromJson json =
|
||||
json
|
||||
|> Json.decodeValue decoder
|
||||
|> Result.withDefault init
|
||||
@ -38,22 +67,28 @@ decoder =
|
||||
(Json.field "counter" Json.int)
|
||||
|
||||
|
||||
save : Storage -> Json.Value
|
||||
save storage =
|
||||
Encode.object
|
||||
[ ( "counter", Encode.int storage.counter )
|
||||
]
|
||||
|
||||
-- Updating storage
|
||||
|
||||
|
||||
|
||||
-- UPDATING STORAGE
|
||||
|
||||
|
||||
increment : Storage -> Storage
|
||||
increment : Storage -> Cmd msg
|
||||
increment storage =
|
||||
{ storage | counter = storage.counter + 1 }
|
||||
|> toJson
|
||||
|> save
|
||||
|
||||
|
||||
decrement : Storage -> Storage
|
||||
decrement : Storage -> Cmd msg
|
||||
decrement storage =
|
||||
{ storage | counter = storage.counter - 1 }
|
||||
|> toJson
|
||||
|> save
|
||||
|
||||
|
||||
|
||||
-- LISTENING FOR STORAGE UPDATES
|
||||
|
||||
|
||||
onChange : (Storage -> msg) -> Sub msg
|
||||
onChange fromStorage =
|
||||
load (\json -> fromJson json |> fromStorage)
|
||||
|
Loading…
Reference in New Issue
Block a user