Composing Elm applications from smaller isolated parts (modules).
You can think of this as a lightweight abstraction built around (model, Cmd msg)
and
(model, Sub msg)
pairs, composing init
, update
, view
and subscribe
using
Cmd.map
,
Sub.map
and Html.map
.
It's recommended to avoid usage of pattern with stateful modules unless there is
clear benefit to choose it. In cases where one would like to use Cmd.map
pattern anyway though, Glue can be used to avoid repeatable patterns for mapping
the msg
types and updating models.
Internal.Glue model subModel msg subMsg
Glue
describes an interface between the parent and child module.
You can create Glue
with the simple
, poly
or glue
function constructors.
Every glue layer is parametrized over:
model
is Model
of parentsubModel
is Model
of childmsg
is Msg
of parentsubMsg
is Msg
of childsimple : { get : model -> subModel, set : subModel -> model -> model } -> Glue model subModel Basics.Never Basics.Never
Simple Glue
constructor for modules that don't produce Cmds.
Note that with this constructor you won't be able to use some functions provided within this library.
poly : { get : model -> subModel, set : subModel -> model -> model } -> Glue model subModel msg msg
A specialized Glue
constructor. Useful when the module's API has
generic msg
type and maps Cmds etc. internally.
This constructor will do nothing to the child msg
s.
glue : { msg : subMsg -> msg, get : model -> subModel, set : subModel -> model -> model } -> Glue model subModel msg subMsg
General Glue
constructor.
Designed for chaining initialization of child modules from parent init
function.
init : Glue model subModel msg subMsg -> ( subModel, Platform.Cmd.Cmd subMsg ) -> ( subModel -> a, Platform.Cmd.Cmd msg ) -> ( a, Platform.Cmd.Cmd msg )
Initialize child module in parent.
type alias Model =
{ message : String
, firstCounterModel : Counter.Model
, secondCounterModel : Counter.Model
}
init : ( Model, Cmd msg )
init =
( Model "", Cmd.none )
|> Glue.init firstCounter Counter.init
|> Glue.init secondCounter Counter.init
update : Glue model subModel msg subMsg -> (a -> subModel -> ( subModel, Platform.Cmd.Cmd subMsg )) -> a -> ( model, Platform.Cmd.Cmd msg ) -> ( model, Platform.Cmd.Cmd msg )
Call the child module update
function with a given message. Useful for
nesting update calls. This function expects the child update
to work with Cmd
s.
-- Child module
updateCounter : Counter.Msg -> Counter.Model -> ( Counter.Model, Cmd Counter.Msg )
updateCounter msg model =
case msg of
Increment ->
( model + 1, Cmd.none )
-- Parent module
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
CounterMsg counterMsg ->
( { model | message = "Counter has changed" }, Cmd.none )
|> Glue.update counter updateCounter counterMsg
updateModel : Glue model subModel msg subMsg -> (a -> subModel -> subModel) -> a -> model -> model
Call the child module update
function with a given message. This function
expects the child update
to not work with Cmd
s.
Note you can use different functions than the child's main update
. For example
the child module might have an updateForRouteChange
function specialized
for a specific parent module situation - you can plug it in here too!
-- Child module
updateCounter : Counter.Msg -> Counter.Model -> Counter.Model
updateCounter msg model =
case msg of
Increment ->
model + 1
-- Parent module
update : Msg -> Model -> Model
update msg model =
case msg of
CounterMsg counterMsg ->
Glue.updateModel counter updateCounter counterMsg model
updateWith : Glue model subModel msg subMsg -> (subModel -> ( subModel, Platform.Cmd.Cmd subMsg )) -> ( model, Platform.Cmd.Cmd msg ) -> ( model, Platform.Cmd.Cmd msg )
Updates the child module with a function other than update
. This function
expects the child function to work with Cmd
s.
increment : Counter.Model -> ( Counter.Model, Cmd Counter.Msg )
increment model =
( model + 1, Cmd.none )
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
IncrementCounter ->
( model, Cmd.none )
|> Glue.updateWith counter increment
updateModelWith : Glue model subModel msg subMsg -> (subModel -> subModel) -> model -> model
Updates the child module with a function other than update
. This function
expects the child function to not work with Cmd
s.
increment : Counter.Model -> Counter.Model
increment model =
model + 1
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
IncrementCounter ->
( model
|> Glue.updateModelWith counter increment
, Cmd.none
)
trigger : Glue model subModel msg subMsg -> (subModel -> Platform.Cmd.Cmd subMsg) -> ( model, Platform.Cmd.Cmd msg ) -> ( model, Platform.Cmd.Cmd msg )
Trigger Cmd in child's function
Commands are async. Therefore trigger doesn't make any update directly.
Use updateModel
over trigger
when you can.
-- Child module
triggerEmit : Counter.Model -> Cmd Counter.Msg
triggerEmit model ->
Task.perform identity <| Task.succeed <| Counter.Emit model
-- Parent module
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
IncrementCounter ->
( model, Cmd.none )
|> Glue.trigger counter triggerIncrement
subscriptions : Glue model subModel msg subMsg -> (subModel -> Platform.Sub.Sub subMsg) -> (model -> Platform.Sub.Sub msg) -> model -> Platform.Sub.Sub msg
Subscribe to the subscriptions
defined in the child module.
subscriptions : Model -> Sub Msg
subscriptions =
(\model -> Mouse.clicks Clicked)
|> Glue.subscriptions foo Foo.subscriptions
|> Glue.subscriptions bar Bar.subscriptions
subscriptionsWhen : (model -> Basics.Bool) -> Glue model subModel msg subMsg -> (subModel -> Platform.Sub.Sub subMsg) -> (model -> Platform.Sub.Sub msg) -> model -> Platform.Sub.Sub msg
Subscribe to child's subscriptions
based on some condition in the parent module.
type alias Model =
{ useCounter : Bool
, counterModel : Counter.Model
}
subscriptions : Model -> Sub Msg
subscriptions =
(\_ -> Mouse.clicks Clicked)
|> Glue.subscriptionsWhen .useCounter counter Counter.subscriptions
view : Glue model subModel msg subMsg -> (subModel -> Html subMsg) -> model -> Html msg
Render child module's view.
view : Model -> Html msg
view model =
Html.div []
[ Html.text model.message
, Glue.view counter Counter.view model
]
viewSimple : Glue model subModel Basics.Never Basics.Never -> (subModel -> Html subMsg) -> (subMsg -> msg) -> model -> Html msg
View the Glue
constructed with the simple
constructor.
Because the Msg
is not part of the Glue definition (Never
type) it needs
to be passed in as a argument.
id : Glue model model msg msg
Identity Glue.
This can be for instance used for chaining updates of model.
chainUpdates model =
( model, Cmd.none )
|> Glue.updateWith Glue.id f
|> Glue.updateWith Glue.id g
combine : Glue model subModel1 msg subMsg1 -> Glue subModel1 subModel2 subMsg1 subMsg2 -> Glue model subModel2 msg subMsg2
Combine two glues into a single glue.
map : (subMsg -> msg) -> ( subModel, Platform.Cmd.Cmd subMsg ) -> ( subModel, Platform.Cmd.Cmd msg )
A tiny abstraction over Cmd.map
packed in (model, Cmd msg)
.