Support for building applications from Elm components
This Elm package is an exploration of how components interact with each other and how to compose components from other components.
The exploration is described in more detail in the index.md.
Each component is structure along the following lines:
module X exposing ( Msg (PublicEvent, ...), Model, init, update, view, subscriptions)
type Msg
= PrivateMsg
| ...
| PublicEvent
| ...
type alias Model =
init: ... -> Model
init ... =
update : Msg -> Model -> Update.Action Msg Model
update msg model =
view : (Msg -> msg) -> Model -> Html msg
view tag model =
subscriptions : (Msg -> msg) -> Model -> Sub msg
subscriptions tag model =
The basic structure follows The Elm Architecture but with a few notable differences:
-
Update functions return a
Update.Action
that can describe a combination of:- updating the model,
- forwarding messages to child components,
- returning events to the parent component,
- request a command to be performed
Use functions in the
Update
module to create these actions. -
The
view
function receive atag
function (also referred to as atagger
function) that is used to convert your component's message type to the parent component's message type.This allows components to be combined at the view level with out introducing any additional messages/events, and with out the container component knowing the workings of the contained component.
See: view-composition.md
-
The
Msg
union type is used to return events to the parent component.There are alternative methods to communicate with the parent component and those are discussed in communicate-with-parent.md. A couple of the alternatives all work with the
Update
functions but only one alternative (perhaps the simplest) has been used in the documentation.The
Msg
type contains both private messages to the component and optionally some public events. Remember to only expose the public events in yourmodule X exposing
list.
TODO: The
init
function cannot yet request commands to be performed.
Lets jump into an example component. This example is part of the full example application found in the examples/2-counter-pair directory.
module CounterPair exposing (Msg, Model, init, update, view)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Component.Update as Update
import Counter
-- MODEL
type Msg
= Reset
| Top Counter.Msg
| Bottom Counter.Msg
type alias Model =
{ top : Counter.Model
, bottom : Counter.Model
}
init : Int -> Int -> Model
init top bottom =
{ top = Counter.init top
, bottom = Counter.init bottom
}
-- UPDATE
update : Msg -> Model -> Update.Action Msg Model
update msg' model =
case msg' of
Reset ->
Update.model (init 0 0)
Top msg ->
Update.component msg model.top (Top) (\x -> { model | top = x }) Counter.update
Bottom msg ->
Update.component msg model.bottom (Bottom) (\x -> { model | bottom = x }) Counter.update
-- VIEW
view : (Msg -> msg) -> Model -> Html msg
view tag model =
div []
[ Counter.view (tag << Top) model.top
, Counter.view (tag << Bottom) model.bottom
, button [ onClick (tag Reset) ] [ text "RESET" ]
]
Things to take note of:
-
The
update
function callsUpdate.model
when it wants to modify the model. A relatedUpdate.ignore
function is useful when you do not want to change the model, or perform any other action. -
Child components have their own
Msg
tag (Top
andBottom
in this example), their own model value (model.top
andmodel.bottom
).The
Update.component
function does the hard work of integrating the child components actions into your own. It will take care of things like not updating your model if the child did not update it's own model.Update.component
needs to be given the childsMsg
tag so it can return events and a function to change your model when the child's model is updated. -
Child views are created with out the use of
Html.App.map
but do require you to combine the passedtag
with the child components tag. For example:Counter.view (tag << Top) model.top
-
Html.Events
also need to be tagged correctly, examples include:type Msg = StartSearch | SearchText Html.div [] [ Html.input [ onInput (tag << SearchText) ] [] , Html.button [ onClick (tag StartSearch) ] [ text "Search" ] ]
This repo contains a few examples showing usage of this Elm package. Download this repo with:
git clone https://github.com/emtenet/elm-component-support.git
Try an example by changing to the example directory and running elm-reactor
:
cd elm-component-support/examples/1-counter
elm-reactor
Now go to http://localhost:8000/ and click on Main.elm
.