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.