Html has beginnerProgram
mostly for learning purposes.
beginnerProgram
is not capable of handling Subscriptions or running Commands.
It is only capable of handling user input from DOM Events.
It only requires a view
to render the model
and an update
function to handle state changes.
Consider this minimal example of beginnerProgram
.
The model
in this example consists of single Int
value.
The update
function has only one branch, which increments the Int
, stored in the model
.
The view
renders the model and attaches click DOM Event.
See how to build the example in Initialize and build
import Html exposing (Html, button, text)
import Html exposing (beginnerProgram)
import Html.Events exposing (onClick)
main : Program Never
main =
beginnerProgram { model = 0, view = view, update = update }
-- UPDATE
type Msg
= Increment
update : Msg -> Int -> Int
update msg model =
case msg of
Increment ->
model + 1
-- VIEW
view : Int -> Html Msg
view model =
button [ onClick Increment ] [ text ("Increment: " ++ (toString model)) ]
program
is a good pick, when your application does not require any external data for initialization.
It is capable of handling Subscriptions and Commands, which enables way more opportunities for handling I/O, such as HTTP communication or interop with JavaScript.
The initial state is required to return start-up Commands along with the Model.
The initialization of program
will require subscriptions
to be provided, along with model
, view
and update
.
See the type definition:
program :
{ init : ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, view : model -> Html msg
}
-> Program Never
The simplest way to illustrate, how you can use Subscriptions is to setup a simple Port communication with JavaScript.
See how to build the example in Initialize and build / Embedding into HTML
port module Main exposing (..)
import Html exposing (Html, text)
import Html exposing (program)
main : Program Never
main =
program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
port input : (Int -> msg) -> Sub msg
-- MODEL
type alias Model =
Int
init : ( Model, Cmd msg )
init =
( 0, Cmd.none )
-- UPDATE
type Msg = Incoming Int
update : Msg -> Model -> ( Model, Cmd msg )
update msg model =
case msg of
Incoming x ->
( x, Cmd.none )
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
input Incoming
-- VIEW
view : Model -> Html msg
view model =
text (toString model)
<!DOCTYPE html>
<html>
<head>
<script src='elm.js'></script>
</head>
<body>
<div id='app'></div>
<script>var app = Elm.Main.embed(document.getElementById('app'));</script>
<button onclick='app.ports.input.send(1);'>send</button>
</body>
</html>
programWithFlags
has only one difference from program
.
It can accept the data upon initialization from JavaScript:
var root = document.body;
var user = { id: 1, name: "Bob" };
var app = Elm.Main.embed( root, user );
The data, passed from JavaScript is called Flags.
In this example we are passing a JavaScript Object to Elm with user information, it is a good practice to specify a Type Alias for flags.
type alias Flags =
{ id: Int
, name: String
}
Flags are passed to the init
function, producing the initial state:
init : Flags -> ( Model, Cmd Msg )
init flags =
let
{ id, name } =
flags
in
( Model id name, Cmd.none )
You might notice the difference from it's type signature:
programWithFlags :
{ init : flags -> ( model, Cmd msg ) -- init now accepts flags
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, view : model -> Html msg
}
-> Program flags
The initialization code looks almost the same, since it's only init
function that is different.
main =
programWithFlags
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
Example demonstrates component composition and one-way message passing from parent to children.
Component composition relies on Message tagging with Html.App.map
In 0.18.0
HTML.App
was collapsed into HTML
Component composition relies on Message tagging with Html.map
See how to build the example in Initialise and build
module Main exposing (..)
import Html exposing (text, div, button, Html)
import Html.Events exposing (onClick)
import Html.App exposing (beginnerProgram)
main =
beginnerProgram
{ view = view
, model = init
, update = update
}
{- In v0.18.0 HTML.App was collapsed into HTML
Use Html.map instead of Html.App.map
-}
view : Model -> Html Msg
view model =
div []
[ Html.App.map FirstCounterMsg (counterView model.firstCounter)
, Html.App.map SecondCounterMsg (counterView model.secondCounter)
, button [ onClick ResetAll ] [ text "Reset counters" ]
]
type alias Model =
{ firstCounter : CounterModel
, secondCounter : CounterModel
}
init : Model
init =
{ firstCounter = 0
, secondCounter = 0
}
type Msg
= FirstCounterMsg CounterMsg
| SecondCounterMsg CounterMsg
| ResetAll
update : Msg -> Model -> Model
update msg model =
case msg of
FirstCounterMsg childMsg ->
{ model | firstCounter = counterUpdate childMsg model.firstCounter }
SecondCounterMsg childMsg ->
{ model | secondCounter = counterUpdate childMsg model.secondCounter }
ResetAll ->
{ model
| firstCounter = counterUpdate Reset model.firstCounter
, secondCounter = counterUpdate Reset model.secondCounter
}
type alias CounterModel =
Int
counterView : CounterModel -> Html CounterMsg
counterView model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, text (toString model)
, button [ onClick Increment ] [ text "+" ]
]
type CounterMsg
= Increment
| Decrement
| Reset
counterUpdate : CounterMsg -> CounterModel -> CounterModel
counterUpdate msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
Reset ->
0
Components define their own Messages, sent after emitted DOM Events, eg. CounterMsg
from Parent-child communication
type CounterMsg
= Increment
| Decrement
| Reset
The view of this component will send messages of CounterMsg
type, therefore the view type signature is Html CounterMsg
.
To be able to reuse counterView
inside parent component's view, we need to pass every CounterMsg
message through parent's Msg
.
This technique is called message tagging.
Parent component must define messages for passing child messages:
type Msg
= FirstCounterMsg CounterMsg
| SecondCounterMsg CounterMsg
| ResetAll
FirstCounterMsg Increment
is a tagged message.
To get a counterView
to send tagged messages, we must use the Html.App.map
function:
Html.map FirstCounterMsg (counterView model.firstCounter)
The HTML.App
package was collapsed into the HTML
package in v0.18.0
To get a counterView
to send tagged messages, we must use the Html.map
function:
Html.map FirstCounterMsg (counterView model.firstCounter)
That changes the type signature Html CounterMsg -> Html Msg
so it's possible to use the counter inside the parent view and handle state updates with parent's update function.