mirror of
https://github.com/correl/elm-mdl.git
synced 2024-12-24 11:50:50 +00:00
Functional component model.
This commit is contained in:
parent
dfda38d0c1
commit
1fdcc78ba7
6 changed files with 249 additions and 192 deletions
|
@ -15,7 +15,7 @@ import Material.Button as Button
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ count : Int
|
{ count : Int
|
||||||
, mdl : Material.Model Action
|
, mdl : Material.Model Action
|
||||||
-- Boilerplate: Model store for any and all MDL components you need.
|
-- Boilerplate: mdl is the Model store for any and all MDL components you need.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ model : Model
|
||||||
model =
|
model =
|
||||||
{ count = 0
|
{ count = 0
|
||||||
, mdl = Material.model
|
, mdl = Material.model
|
||||||
-- Always use this initial MDL component model store.
|
-- Boilerplate: Always use this initial MDL model store.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ type Action
|
||||||
| Reset
|
| Reset
|
||||||
| MDL (Material.Action Action)
|
| MDL (Material.Action Action)
|
||||||
-- Boilerplate: Action for MDL actions (ripple animations etc.).
|
-- Boilerplate: Action for MDL actions (ripple animations etc.).
|
||||||
-- It should always look like this.
|
|
||||||
|
|
||||||
|
|
||||||
update : Action -> Model -> (Model, Effects.Effects Action)
|
update : Action -> Model -> (Model, Effects.Effects Action)
|
||||||
|
@ -83,8 +82,7 @@ button. The arguments are:
|
||||||
-}
|
-}
|
||||||
increase : Button.Instance Mdl Action
|
increase : Button.Instance Mdl Action
|
||||||
increase =
|
increase =
|
||||||
Button.instance 0 MDL
|
Button.instance 0 MDL Button.flat (Button.model True)
|
||||||
Button.flat (Button.model True)
|
|
||||||
[ Button.fwdClick Increase ]
|
[ Button.fwdClick Increase ]
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,8 +91,7 @@ click event to our Reset action.
|
||||||
-}
|
-}
|
||||||
reset : Button.Instance Mdl Action
|
reset : Button.Instance Mdl Action
|
||||||
reset =
|
reset =
|
||||||
Button.instance 1 MDL
|
Button.instance 1 MDL Button.flat (Button.model False)
|
||||||
Button.flat (Button.model False)
|
|
||||||
[ Button.fwdClick Reset ]
|
[ Button.fwdClick Reset ]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,8 @@ import Demo.Page as Page
|
||||||
-- MODEL
|
-- MODEL
|
||||||
|
|
||||||
|
|
||||||
type alias Mdl = Material.Model Action
|
type alias Mdl =
|
||||||
|
Material.Model Action
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
|
@ -48,22 +49,8 @@ type Action
|
||||||
| MDL (Material.Action Action)
|
| MDL (Material.Action Action)
|
||||||
|
|
||||||
|
|
||||||
snackbar : Int -> Snackbar.Contents Action
|
add : Model -> (Int -> Snackbar.Contents Action) -> (Model, Effects Action)
|
||||||
snackbar k =
|
add model f =
|
||||||
Snackbar.snackbar
|
|
||||||
("Snackbar message #" ++ toString k)
|
|
||||||
"UNDO"
|
|
||||||
(Undo k)
|
|
||||||
|
|
||||||
|
|
||||||
toast : Int -> Snackbar.Contents Action
|
|
||||||
toast k =
|
|
||||||
Snackbar.toast
|
|
||||||
<| "Toast message #" ++ toString k
|
|
||||||
|
|
||||||
|
|
||||||
add : (Int -> Snackbar.Contents Action) -> Model -> (Model, Effects Action)
|
|
||||||
add f model =
|
|
||||||
let
|
let
|
||||||
(mdl', fx) =
|
(mdl', fx) =
|
||||||
Snackbar.add (f model.count) snackbarComponent model.mdl
|
Snackbar.add (f model.count) snackbarComponent model.mdl
|
||||||
|
@ -81,10 +68,12 @@ update : Action -> Model -> (Model, Effects Action)
|
||||||
update action model =
|
update action model =
|
||||||
case action of
|
case action of
|
||||||
AddSnackbar ->
|
AddSnackbar ->
|
||||||
add snackbar model
|
add model
|
||||||
|
<| \k -> Snackbar.snackbar ("Snackbar message #" ++ toString k) "UNDO" (Undo k)
|
||||||
|
|
||||||
AddToast ->
|
AddToast ->
|
||||||
add toast model
|
add model
|
||||||
|
<| \k -> Snackbar.toast <| "Toast message #" ++ toString k
|
||||||
|
|
||||||
Undo k ->
|
Undo k ->
|
||||||
({ model
|
({ model
|
||||||
|
@ -96,18 +85,19 @@ update action model =
|
||||||
Material.update MDL action' model.mdl
|
Material.update MDL action' model.mdl
|
||||||
|> map1st (\m -> { model | mdl = m })
|
|> map1st (\m -> { model | mdl = m })
|
||||||
|
|
||||||
|
|
||||||
-- VIEW
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
addSnackbar : Button.Instance Mdl Action
|
addSnackbarButton : Button.Instance Mdl Action
|
||||||
addSnackbar =
|
addSnackbarButton =
|
||||||
Button.instance 0 MDL
|
Button.instance 0 MDL
|
||||||
Button.raised (Button.model True)
|
Button.raised (Button.model True)
|
||||||
[ Button.fwdClick AddSnackbar ]
|
[ Button.fwdClick AddSnackbar ]
|
||||||
|
|
||||||
|
|
||||||
addToast : Button.Instance Mdl Action
|
addToastButton : Button.Instance Mdl Action
|
||||||
addToast =
|
addToastButton =
|
||||||
Button.instance 1 MDL
|
Button.instance 1 MDL
|
||||||
Button.raised (Button.model True)
|
Button.raised (Button.model True)
|
||||||
[ Button.fwdClick AddToast ]
|
[ Button.fwdClick AddToast ]
|
||||||
|
@ -115,8 +105,7 @@ addToast =
|
||||||
|
|
||||||
snackbarComponent : Snackbar.Instance Mdl Action
|
snackbarComponent : Snackbar.Instance Mdl Action
|
||||||
snackbarComponent =
|
snackbarComponent =
|
||||||
Snackbar.instance 2 MDL Snackbar.model
|
Snackbar.instance MDL Snackbar.model
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
clickView : Model -> Int -> Html
|
clickView : Model -> Int -> Html
|
||||||
|
@ -164,11 +153,11 @@ view addr model =
|
||||||
-- to add css/classes to top-level element of components (div
|
-- to add css/classes to top-level element of components (div
|
||||||
-- in grid, button in button, div in textfield etc.)
|
-- in grid, button in button, div in textfield etc.)
|
||||||
[ cell [ size All 2, size Phone 2, align Top ]
|
[ cell [ size All 2, size Phone 2, align Top ]
|
||||||
[ addToast.view addr model.mdl [] [ text "Toast" ]
|
[ addToastButton.view addr model.mdl [] [ text "Toast" ]
|
||||||
]
|
]
|
||||||
, cell
|
, cell
|
||||||
[ size All 2, size Phone 2, align Top ]
|
[ size All 2, size Phone 2, align Top ]
|
||||||
[ addSnackbar.view addr model.mdl [] [ text "Snackbar" ]
|
[ addSnackbarButton.view addr model.mdl [] [ text "Snackbar" ]
|
||||||
]
|
]
|
||||||
, cell
|
, cell
|
||||||
[ size Desktop 7, size Tablet 3, size Phone 12, align Top ]
|
[ size Desktop 7, size Tablet 3, size Phone 12, align Top ]
|
||||||
|
|
147
src/Material.elm
147
src/Material.elm
|
@ -15,27 +15,28 @@ for a live demo.
|
||||||
|
|
||||||
# Component model
|
# Component model
|
||||||
|
|
||||||
The component model of the library is simply the Elm Architecture, e.g.,
|
The component model of the library is simply the Elm Architecture (TEA), i.e.,
|
||||||
each component has Model, Action, view, and update. A minimal example using
|
each component has types `Model` and `Action`, and values `view` and `update`. A
|
||||||
this library in plain Elm Architecture can be found
|
minimal example using this library in plain TEA can be found
|
||||||
[here](https://github.com/debois/elm-mdl/blob/master/examples/Component-EA.elm).
|
[here](https://github.com/debois/elm-mdl/blob/master/examples/Component-TEA.elm).
|
||||||
|
|
||||||
Nesting large amounts of components in the Elm Architecture is somewhat
|
Using more than a few component in plain TEA is unwieldy because of the large
|
||||||
unwieldy because of the large amount of boilerplate one has to write. This
|
amount of boilerplate one has to write. This library provides the "component
|
||||||
library includes "component support", for getting rid of most of that
|
support" for getting rid of most of that boilerplate. A minimal example using
|
||||||
boilerplate. A minimal example using component support is
|
component support is
|
||||||
[here](http://github.com/debois/elm-mdl/blob/master/examples/Component.elm).
|
[here](http://github.com/debois/elm-mdl/blob/master/examples/Component.elm).
|
||||||
|
|
||||||
It is important to note that component support lives __within__ the Elm
|
It is important to note that component support lives __within__ TEA;
|
||||||
architecture; it is not an alternative architecture.
|
it is not an alternative architecture.
|
||||||
|
|
||||||
# Getting started
|
# Getting started
|
||||||
|
|
||||||
The easiest way to get started is to start with one of the minimal examples above.
|
The easiest way to get started is to start with one of the minimal examples above.
|
||||||
We recommend going with the library's component support rather than working
|
We recommend going with the library's
|
||||||
directly in plain Elm Architecture.
|
[component support](http://github.com/debois/elm-mdl/blob/master/examples/Component.elm)
|
||||||
|
rather than working directly in plain Elm Architecture.
|
||||||
|
|
||||||
# This module
|
# Component Support
|
||||||
|
|
||||||
This module contains only convenience functions for working with nested
|
This module contains only convenience functions for working with nested
|
||||||
components in the Elm architecture. A minimal example using this library
|
components in the Elm architecture. A minimal example using this library
|
||||||
|
@ -49,69 +50,69 @@ All examples in this subsection is from the
|
||||||
Here is how you use component support in general. First, boilerplate.
|
Here is how you use component support in general. First, boilerplate.
|
||||||
|
|
||||||
1. Include `Material`:
|
1. Include `Material`:
|
||||||
`import Material`
|
|
||||||
|
`import Material`
|
||||||
|
|
||||||
2. Add a model container Material components to your model:
|
2. Add a model container Material components to your model:
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ ...
|
{ ...
|
||||||
, mdl : Material.Model
|
, mdl : Material.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
model : Model =
|
model : Model =
|
||||||
{ ...
|
{ ...
|
||||||
, mdl = Material.model
|
, mdl = Material.model
|
||||||
}
|
}
|
||||||
|
|
||||||
3. Add an action for Material components.
|
3. Add an action for Material components.
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
...
|
...
|
||||||
| MDL (Material.Action Action)
|
| MDL (Material.Action Action)
|
||||||
|
|
||||||
4. Handle that action in your update function as follows:
|
4. Handle that action in your update function as follows:
|
||||||
|
|
||||||
update action model =
|
update action model =
|
||||||
case action of
|
case action of
|
||||||
...
|
...
|
||||||
MDL action' ->
|
MDL action' ->
|
||||||
let (mdl', fx) =
|
let (mdl', fx) =
|
||||||
Material.update MDL action' model.mdl
|
Material.update MDL action' model.mdl
|
||||||
in
|
in
|
||||||
( { model | mdl = mdl' } , fx )
|
( { model | mdl = mdl' } , fx )
|
||||||
|
|
||||||
|
|
||||||
Next, make the component instances you need. Do this in the View section of your
|
Next, make the component instances you need. Do this in the View section of your
|
||||||
source file. Let's say you need a textfield for name entry, and you'd like to
|
source file. Let's say you need a textfield for name entry, and you'd like to
|
||||||
be notifed whenever the field changes value through your own NameChanged action:
|
be notifed whenever the field changes value through your own NameChanged action:
|
||||||
|
|
||||||
import Material.Textfield as Textfield
|
import Material.Textfield as Textfield
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
type Action =
|
|
||||||
...
|
|
||||||
| NameChanged String
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
update action model =
|
|
||||||
case action of
|
|
||||||
...
|
...
|
||||||
NameChanged name ->
|
|
||||||
-- Do whatever you need to do.
|
|
||||||
|
|
||||||
...
|
type Action =
|
||||||
|
...
|
||||||
|
| NameChanged String
|
||||||
|
|
||||||
nameInput : Textfield.Instance Material.Model Action
|
...
|
||||||
nameInput =
|
|
||||||
Textfield.instance 2 MDL Textfield.model
|
|
||||||
[ Textfield.fwdInput NameChanged ]
|
|
||||||
|
|
||||||
|
update action model =
|
||||||
view addr model =
|
case action of
|
||||||
...
|
...
|
||||||
nameInput.view addr model.mdl
|
NameChanged name ->
|
||||||
|
-- Do whatever you need to do.
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
nameInput : Textfield.Instance Material.Model Action
|
||||||
|
nameInput =
|
||||||
|
Textfield.instance 2 MDL Textfield.model
|
||||||
|
[ Textfield.fwdInput NameChanged
|
||||||
|
]
|
||||||
|
|
||||||
|
view addr model =
|
||||||
|
...
|
||||||
|
nameInput.view addr model.mdl
|
||||||
|
|
||||||
|
|
||||||
The win relative to using plain Elm Architecture is that adding a component
|
The win relative to using plain Elm Architecture is that adding a component
|
||||||
|
@ -125,13 +126,13 @@ but now it's not boilerplate, its "business logic".)
|
||||||
Using this module will force all elm-mdl components to be built and included in
|
Using this module will force all elm-mdl components to be built and included in
|
||||||
your application. If this is unacceptable, you can custom-build a version of this
|
your application. If this is unacceptable, you can custom-build a version of this
|
||||||
module that uses only the components you need. To do so, you need to re-implement
|
module that uses only the components you need. To do so, you need to re-implement
|
||||||
the present module, modifying the values `model` and `Model`. The module source
|
the present module, modifying the values `model` and `Model` by commenting out the
|
||||||
can be found
|
components you are not using. The module source can be found
|
||||||
[here](https://github.com/debois/elm-mdl/blob/master/src/Material.elm).
|
[here](https://github.com/debois/elm-mdl/blob/master/src/Material.elm).
|
||||||
|
|
||||||
You do not need to re-build the entire elm-mdl library; simply copy the
|
You do not need to re-build the entire elm-mdl library; simply copy the
|
||||||
source of this module, give it a new name, modify as itMatendicated above, then use
|
source of this module, give it a new name, modify as it as indicated above,
|
||||||
your modified module rather than this one.
|
then use your modified module rather than this one.
|
||||||
|
|
||||||
@docs Model, model, Action, update
|
@docs Model, model, Action, update
|
||||||
-}
|
-}
|
||||||
|
@ -145,37 +146,39 @@ import Material.Snackbar as Snackbar
|
||||||
import Material.Component as Component exposing (Indexed)
|
import Material.Component as Component exposing (Indexed)
|
||||||
|
|
||||||
|
|
||||||
{-| Model encompassing all Material components.
|
{-| Model encompassing all Material components. Since some components store
|
||||||
|
user actions in their model (notably Snackbar), the model is generic in the
|
||||||
|
type of such "observations".
|
||||||
-}
|
-}
|
||||||
type alias Model a =
|
type alias Model obs =
|
||||||
{ button : Indexed Button.Model
|
{ button : Indexed Button.Model
|
||||||
, textfield : Indexed Textfield.Model
|
, textfield : Indexed Textfield.Model
|
||||||
, snackbar : Indexed (Snackbar.Model a)
|
, snackbar : Maybe (Snackbar.Model obs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Initial model.
|
{-| Initial model.
|
||||||
-}
|
-}
|
||||||
model : Model a
|
model : Model obs
|
||||||
model =
|
model =
|
||||||
{ button = Dict.empty
|
{ button = Dict.empty
|
||||||
, textfield = Dict.empty
|
, textfield = Dict.empty
|
||||||
, snackbar = Dict.empty
|
, snackbar = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Action encompassing actions of all Material components.
|
{-| Action encompassing actions of all Material components.
|
||||||
-}
|
-}
|
||||||
type alias Action action =
|
type alias Action obs =
|
||||||
Component.Action (Model action) action
|
Component.Action (Model obs) obs
|
||||||
|
|
||||||
|
|
||||||
{-| Update function for the above Action.
|
{-| Update function for the above Action.
|
||||||
-}
|
-}
|
||||||
update :
|
update :
|
||||||
(Action action -> action)
|
(Action obs -> obs)
|
||||||
-> Action action
|
-> Action obs
|
||||||
-> Model action
|
-> Model obs
|
||||||
-> (Model action, Effects action)
|
-> (Model obs, Effects obs)
|
||||||
update =
|
update =
|
||||||
Component.update
|
Component.update
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
module Material.Component
|
module Material.Component
|
||||||
( Embedding, Observer
|
( embed, embedIndexed, Embedding, Observer
|
||||||
, Indexed
|
, Indexed
|
||||||
, Instance, instance
|
, Instance, instance, instance1
|
||||||
, update
|
, update
|
||||||
, Action
|
, Action
|
||||||
) where
|
) where
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
|
||||||
The Elm Architecture is conceptually very nice, but it forces us
|
The Elm Architecture is conceptually very nice, but it forces us to write large
|
||||||
to write large amounts of boilerplate whenever we need to use a "component".
|
amounts of boilerplate whenever we need to use a "component". We must:
|
||||||
We must:
|
|
||||||
|
|
||||||
1. Retain the state of the component in our Model
|
1. Retain the state of the component in our Model
|
||||||
2. Add the components actions to our Action
|
2. Add the components actions to our Action
|
||||||
|
@ -26,37 +25,43 @@ This module provides an extensible mechanism for collecting arbitrary
|
||||||
a single Action type and update function. The module is used internally to
|
a single Action type and update function. The module is used internally to
|
||||||
produce `instance` functions; if you are using elm-mdl (and are not interested in
|
produce `instance` functions; if you are using elm-mdl (and are not interested in
|
||||||
optimising for compiled program size), you should ignore this module and look
|
optimising for compiled program size), you should ignore this module and look
|
||||||
instead at `Material`.
|
instead at `Material`.
|
||||||
|
|
||||||
# Component types
|
|
||||||
@docs Indexed, Embedding, Observer, Instance
|
# Embeddings
|
||||||
|
@docs Indexed, Embedding, embed, embedIndexed
|
||||||
|
|
||||||
# Instance construction
|
# Instance construction
|
||||||
@docs instance
|
@docs Action, Instance, Observer, instance, instance1
|
||||||
|
|
||||||
# Instance consumption
|
# Instance consumption
|
||||||
@docs update, Action
|
@docs update
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import Effects exposing (Effects)
|
import Effects exposing (Effects)
|
||||||
|
import Task
|
||||||
import Dict exposing (Dict)
|
import Dict exposing (Dict)
|
||||||
|
|
||||||
import Material.Helpers exposing (map1, map2, map1st, map2nd, Update, Update')
|
import Material.Helpers exposing (map1, map2, map1st, map2nd, Update, Update')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- TYPES
|
-- TYPES
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{-| Standard EA view function type.
|
{-| Standard EA view function type.
|
||||||
-}
|
-}
|
||||||
type alias View model action a =
|
type alias View model action a =
|
||||||
Signal.Address action -> model -> a
|
Signal.Address action -> model -> a
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- EMBEDDING MODELS
|
-- EMBEDDING MODELS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{-| Indexed families of things.
|
{-| Indexed families of things.
|
||||||
-}
|
-}
|
||||||
type alias Indexed a =
|
type alias Indexed a =
|
||||||
|
@ -65,9 +70,9 @@ type alias Indexed a =
|
||||||
|
|
||||||
{-| An __embedding__ of an Elm Architecture component is a variant in which
|
{-| An __embedding__ of an Elm Architecture component is a variant in which
|
||||||
view and update functions know how to extract and update their model
|
view and update functions know how to extract and update their model
|
||||||
from a larger container model.
|
from a larger master model.
|
||||||
-}
|
-}
|
||||||
type alias Embedding model container action a =
|
type alias Embedding model container action a =
|
||||||
{ view : View container action a
|
{ view : View container action a
|
||||||
, update : Update container action
|
, update : Update container action
|
||||||
, getModel : container -> model
|
, getModel : container -> model
|
||||||
|
@ -78,6 +83,14 @@ type alias Embedding model container action a =
|
||||||
{-| Embed a component. Third and fourth arguments are a getter (extract the
|
{-| Embed a component. Third and fourth arguments are a getter (extract the
|
||||||
local model from the container) and a setter (update local model in the
|
local model from the container) and a setter (update local model in the
|
||||||
container).
|
container).
|
||||||
|
|
||||||
|
It is instructive to compare the types of the view and update function in
|
||||||
|
the input and output:
|
||||||
|
|
||||||
|
{- Input -} {- Output -}
|
||||||
|
View model action a View container action a
|
||||||
|
Update model action Update container action
|
||||||
|
|
||||||
-}
|
-}
|
||||||
embed :
|
embed :
|
||||||
View model action a -> -- Given a view function,
|
View model action a -> -- Given a view function,
|
||||||
|
@ -98,11 +111,9 @@ embed view update get set =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| We are interested in particular embeddings where components of the same type
|
{-| We are interested in particular embeddings where components of the same
|
||||||
all have their state living inside a shared `Dict`; the individual component
|
type all have their state living inside a shared `Dict`; the individual
|
||||||
has an id used for looking up its own state. Its the responsibility of the user
|
component has a key used to look up its own state.
|
||||||
to make
|
|
||||||
sure that ids are unique.
|
|
||||||
-}
|
-}
|
||||||
embedIndexed :
|
embedIndexed :
|
||||||
View model action a -> -- Given a view function,
|
View model action a -> -- Given a view function,
|
||||||
|
@ -128,37 +139,90 @@ embedIndexed view update get set model0 id =
|
||||||
-- LIFTING ACTIONS
|
-- LIFTING ACTIONS
|
||||||
|
|
||||||
|
|
||||||
{-| Generic MDL Action.
|
|
||||||
|
{-| Similarly to how embeddings enable collecting models of different type
|
||||||
|
in a single model container, we need to collect actions in a single "master
|
||||||
|
action" type. Obviously, actions need to be eventually executed by running
|
||||||
|
the corresponding update function. To avoid this master action type explicitly
|
||||||
|
representing the Action/update pairs of elm-mdl components, we represent an
|
||||||
|
action of an individual component as a partially applied update function; that
|
||||||
|
is, a function `container -> container`. E.g., the `Click` action of Button is
|
||||||
|
conceptually represented as:
|
||||||
|
|
||||||
|
embeddedButton : Embedding Button.Model container action ...
|
||||||
|
embeddedButton =
|
||||||
|
embedIndexed
|
||||||
|
Button.view Button.update .button {\m x -> {m|button=x} Button.model 0
|
||||||
|
|
||||||
|
clickAction : container -> container
|
||||||
|
clickAction = embeddedButton.update Button.click
|
||||||
|
|
||||||
|
When all Material components are embedded in the same `container` model, we
|
||||||
|
then have a uniform update mechanism.
|
||||||
|
|
||||||
|
We lost the ability to inspect the action when we did this, though. To be
|
||||||
|
able to react to some actions of a component, we add to our `container ->
|
||||||
|
container` type for actions a potential __observation__ of type `obs`.
|
||||||
|
In practice, this observation type `obs` will be the Action of the TEA
|
||||||
|
component __hosting__ MDL components.
|
||||||
|
|
||||||
|
Altogether, accounting also for effects, we arrive at the following type.
|
||||||
-}
|
-}
|
||||||
type Action model obs =
|
type Action container obs =
|
||||||
A (model -> (model, Effects (Action model obs), Maybe obs))
|
A (container -> (container, Effects (Action container obs), Maybe obs))
|
||||||
|
|
||||||
|
|
||||||
|
{-| Type of observers, i.e., functions that take an actual action of the
|
||||||
|
underlying TEA component to an observation. E.g., Button has an Observer for
|
||||||
|
its `Click` action.
|
||||||
|
-}
|
||||||
|
type alias Observer action obs =
|
||||||
|
action -> Maybe obs
|
||||||
|
|
||||||
|
|
||||||
{-| Generic update function for Action.
|
{-| Generic update function for Action.
|
||||||
-}
|
-}
|
||||||
update :
|
update :
|
||||||
(Action state action -> action) ->
|
(Action container obs -> obs) ->
|
||||||
Update' state (Action state action) action
|
Update' container (Action container obs) obs
|
||||||
|
|
||||||
update fwd (A f) state =
|
update fwd (A f) container =
|
||||||
let
|
let
|
||||||
(state', fx, obs) =
|
(container', fx, obs) =
|
||||||
f state
|
f container
|
||||||
|> map2 (Effects.map fwd)
|
|> map2 (Effects.map fwd)
|
||||||
in
|
in
|
||||||
case obs of
|
case obs of
|
||||||
Nothing ->
|
Nothing ->
|
||||||
(state', fx)
|
(container', fx)
|
||||||
|
|
||||||
Just x ->
|
Just x ->
|
||||||
(state', Effects.batch [ fx, Effects.tick (always x) ])
|
(container', Effects.batch [ fx, Effects.task (Task.succeed x) ])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- INSTANCES
|
-- INSTANCES
|
||||||
|
|
||||||
|
|
||||||
{- EA update function variant where running the function
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{-| Type of component instances. A component instance contains a view,
|
||||||
|
get/set/map for the inner model, and a forwarder lifting component
|
||||||
|
actions to observations.
|
||||||
|
-}
|
||||||
|
type alias Instance model container action obs a =
|
||||||
|
{ view : View container obs a
|
||||||
|
, get : container -> model
|
||||||
|
, set : model -> container -> container
|
||||||
|
, map : (model -> model) -> container -> container
|
||||||
|
, fwd : action -> obs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{- TEA update function variant where running the function
|
||||||
produces not just a new model and an effect, but also an
|
produces not just a new model and an effect, but also an
|
||||||
observation.
|
observation.
|
||||||
-}
|
-}
|
||||||
|
@ -166,34 +230,13 @@ type alias Step model action obs =
|
||||||
action -> model -> (model, Effects action, Maybe obs)
|
action -> model -> (model, Effects action, Maybe obs)
|
||||||
|
|
||||||
|
|
||||||
|
{- Partially apply a step function to an action, producing a generic Action.
|
||||||
{-| Type of component instances. A component instance contains a view,
|
|
||||||
and get/set/map for, well, getting, setting, and mapping the component
|
|
||||||
model.
|
|
||||||
-}
|
|
||||||
type alias Instance submodel model subaction action a =
|
|
||||||
{ view : View model action a
|
|
||||||
, get : model -> submodel
|
|
||||||
, set : submodel -> model -> model
|
|
||||||
, map : (submodel -> submodel) -> model -> model
|
|
||||||
, fwd : subaction -> action
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{- Partially apply a step function to an action,
|
|
||||||
producing a generic Action.
|
|
||||||
-}
|
-}
|
||||||
pack : (Step model action obs) -> action -> Action model obs
|
pack : (Step model action obs) -> action -> Action model obs
|
||||||
pack update action =
|
pack update action =
|
||||||
A (update action >> map2 (Effects.map (pack update)))
|
A (update action >> map2 (Effects.map (pack update)))
|
||||||
|
|
||||||
|
|
||||||
{-| Type of observers.
|
|
||||||
-}
|
|
||||||
type alias Observer action obs =
|
|
||||||
action -> Maybe obs
|
|
||||||
|
|
||||||
|
|
||||||
{- Convert an update function to a step function by applying a
|
{- Convert an update function to a step function by applying a
|
||||||
function that converts the action input to the update function into
|
function that converts the action input to the update function into
|
||||||
an observation.
|
an observation.
|
||||||
|
@ -216,20 +259,22 @@ pick f xs =
|
||||||
x -> x
|
x -> x
|
||||||
|
|
||||||
|
|
||||||
connect : List (Observer subaction action) -> Observer subaction action
|
{- Promote a list of Observers to a single Observer by picking, for a given
|
||||||
|
action, the first one that succeeds.
|
||||||
|
-}
|
||||||
|
connect : List (Observer action obs) -> Observer action obs
|
||||||
connect observers subaction =
|
connect observers subaction =
|
||||||
pick ((|>) subaction) observers
|
pick ((|>) subaction) observers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{-| Given a lifting function, a list of observers and an embedding, construct an
|
{-| Given a lifting function, a list of observers and an embedding, construct an
|
||||||
Instance. Notice that the Instance forgets the type parameter `subaction`.
|
Instance.
|
||||||
-}
|
-}
|
||||||
instance' :
|
instance'
|
||||||
(Action model action -> action) ->
|
: (Action container obs -> obs)
|
||||||
List (Observer subaction action) ->
|
-> List (Observer action obs)
|
||||||
Embedding submodel model subaction a ->
|
-> Embedding model container action a
|
||||||
Instance submodel model subaction action a
|
-> Instance model container action obs a
|
||||||
instance' lift observers embedding =
|
instance' lift observers embedding =
|
||||||
let
|
let
|
||||||
fwd =
|
fwd =
|
||||||
|
@ -249,31 +294,58 @@ instance' lift observers embedding =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{-| It is helpful to see parameter names:
|
{-| It is helpful to see parameter names:
|
||||||
|
|
||||||
instance view update get set id lift model0 observers =
|
instance view update get set id lift model0 observers =
|
||||||
...
|
...
|
||||||
|
|
||||||
Convert a regular Elm Architecture component (view, update) to a component
|
Convert a regular Elm Architecture component (`view`, `update`) to a component
|
||||||
which knows how to access its state in a generic container model (get, set),
|
which knows how to access its model inside a generic container model (`get`,
|
||||||
and which dispatches generic Action updates, lifted to the consumers action
|
`set`), and which dispatches generic `Action` updates, lifted to the consumers
|
||||||
type (lift). You can react to actions in custom way by providing observers
|
action type `obs` (`lift`). You can react to actions in custom way by providing
|
||||||
(observers). You must also provide an initial model (model0) and an identifier
|
observers (`observers`). You must also provide an initial model (`model0`) and an
|
||||||
for the instance (id). The identifier must be unique for all instances of the
|
identifier for the instance (`id`). The identifier must be unique for all
|
||||||
same type stored in the same model (rule of thumb: if they are in the same
|
instances of the same type stored in the same model (overapproximating rule of
|
||||||
file, they need distinct ids.)
|
thumb: if they are in the same file, they need distinct ids.)
|
||||||
|
|
||||||
|
Its instructive to compare the types of the input and output views:
|
||||||
|
|
||||||
|
{- Input -} {- Output -}
|
||||||
|
View model action a View container obs a
|
||||||
|
|
||||||
|
That is, this function fully converts a view from its own `model` and `action`
|
||||||
|
to the master `container` model and `observation` action.
|
||||||
-}
|
-}
|
||||||
instance
|
instance
|
||||||
: View model action a
|
: View model action a
|
||||||
-> Update model action
|
-> Update model action
|
||||||
-> (container -> Indexed model)
|
-> (container -> Indexed model)
|
||||||
-> (Indexed model -> container -> container)
|
-> (Indexed model -> container -> container)
|
||||||
-> Int
|
-> Int
|
||||||
-> (Action container observation -> observation)
|
-> (Action container obs -> obs)
|
||||||
-> model
|
-> model
|
||||||
-> List (Observer action observation)
|
-> List (Observer action obs)
|
||||||
-> Instance model container action observation a
|
-> Instance model container action obs a
|
||||||
|
|
||||||
instance view update get set id lift model0 observers =
|
instance view update get set id lift model0 observers =
|
||||||
embedIndexed view update get set model0 id
|
embedIndexed view update get set model0 id
|
||||||
|> instance' lift observers
|
|> instance' lift observers
|
||||||
|
|
||||||
|
|
||||||
|
{-| Variant of `instance` for components that are naturally singletons
|
||||||
|
(e.g., snackbar, layout).
|
||||||
|
-}
|
||||||
|
instance1
|
||||||
|
: View model action a
|
||||||
|
-> Update model action
|
||||||
|
-> (container -> Maybe model)
|
||||||
|
-> (Maybe model -> container -> container)
|
||||||
|
-> (Action container obs -> obs)
|
||||||
|
-> model
|
||||||
|
-> List (Observer action obs)
|
||||||
|
-> Instance model container action obs a
|
||||||
|
|
||||||
|
instance1 view update get set lift model0 observers =
|
||||||
|
embed view update (get >> Maybe.withDefault model0) (Just >> set)
|
||||||
|
|> instance' lift observers
|
||||||
|
|
|
@ -281,7 +281,7 @@ view addr model =
|
||||||
{-|
|
{-|
|
||||||
-}
|
-}
|
||||||
type alias State s obs =
|
type alias State s obs =
|
||||||
{ s | snackbar : Indexed (Model obs) }
|
{ s | snackbar : Maybe (Model obs) }
|
||||||
|
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
@ -306,19 +306,16 @@ actionObserver action =
|
||||||
Nothing
|
Nothing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{-| Component instance.
|
{-| Component instance.
|
||||||
-}
|
-}
|
||||||
instance :
|
instance
|
||||||
Int
|
: (Component.Action (State state obs) obs -> obs)
|
||||||
-> (Component.Action (State state obs) obs -> obs)
|
|
||||||
-> (Model obs)
|
-> (Model obs)
|
||||||
-> Instance (State state obs) obs
|
-> Instance (State state obs) obs
|
||||||
|
|
||||||
instance id lift model0 =
|
instance lift model0 =
|
||||||
Component.instance
|
Component.instance1
|
||||||
view update .snackbar (\x y -> {y | snackbar = x}) id lift model0 [ actionObserver ]
|
view update .snackbar (\x y -> {y | snackbar = x}) lift model0 [ actionObserver ]
|
||||||
|
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
TODO
|
TODO
|
||||||
|
@ -334,4 +331,3 @@ add contents inst model =
|
||||||
update (Add contents) (inst.get model)
|
update (Add contents) (inst.get model)
|
||||||
in
|
in
|
||||||
(inst.set sb model, Effects.map inst.fwd fx)
|
(inst.set sb model, Effects.map inst.fwd fx)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue