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.Actionthat 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
Updatemodule to create these actions. -
The
viewfunction receive atagfunction (also referred to as ataggerfunction) 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
Msgunion 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
Updatefunctions but only one alternative (perhaps the simplest) has been used in the documentation.The
Msgtype contains both private messages to the component and optionally some public events. Remember to only expose the public events in yourmodule X exposinglist.
TODO: The
initfunction 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
updatefunction callsUpdate.modelwhen it wants to modify the model. A relatedUpdate.ignorefunction is useful when you do not want to change the model, or perform any other action. -
Child components have their own
Msgtag (TopandBottomin this example), their own model value (model.topandmodel.bottom).The
Update.componentfunction 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.componentneeds to be given the childsMsgtag 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.mapbut do require you to combine the passedtagwith the child components tag. For example:Counter.view (tag << Top) model.top
-
Html.Eventsalso 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.gitTry an example by changing to the example directory and running elm-reactor:
cd elm-component-support/examples/1-counter
elm-reactorNow go to http://localhost:8000/ and click on Main.elm.