This is some serious stuff
elm architecture
module Main exposing (..)
module DataModels.Color exposing (..)
-- exposing everything
exposing (..)
-- exposing certain functions
exposing (init, encode, decoder)
-- exposing union type
exposing (Color)
-- exposing union type AND type values
exposing (Color(..))
import DataModels.Color
> DataModels.Color.Blue
import DataModels.Color exposing (..)
> Blue
import DataModels.Color exposing (init, toString)
> Blue -- not found
import DataModels.Color as Color
> Color.Blue
import DataModels.Color as Color exposing (Color, colorDecoder)
> giveMeRed : Color
giveMeRed =
Color.Red
> Color.init
> Color.toString
> colorDecoder
and
Communication with the outside world
Side effects? 👎
Managed effects. 👍
Cmd and Sub modules have functions that work nicely with them
Program flags model msg
Example with an element
{ init : flags -> ( model, Cmd msg )
, view : model -> Html msg
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
}
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
init : flags -> ( model, Cmd msg )
type alias InputParams = { currentTime : String }
type alias Model =
{ timeOfInitialization : String
, messages : List String
, typedValue : String
}
init : InputParams -> ( Model, Cmd msg )
init inputParams =
( { timeOfInitialization = inputParams.currentTime
, messages = []
, typedValue = ""
}
, Cmd.none
)
Initializing program without flags?
Unit type.
Program () Model Msg
...
init : () -> ( Model, Cmd msg )
init _ =
...
view : model -> Html msg
import Html exposing (Html)
...
view : model -> Html msg
Elm manages its own Virtual DOM
Html and Html.Attributes modules have a function for every HTML element / attribute
This is some
serious
stuff
import Html exposing (Html)
import Html.Attributes as Attr
...
view : Model -> Html msg
view model =
Html.div [ Attr.id "main", Attr.class "blue" ]
[
Html.p [ Attr.class "red" ]
[
Html.text "This is some"
, Html.span [ Attr.class "purple-text" ]
[ Html.text "serious" ]
, Html.text "stuff"
]
]
import Html.Events exposing (onInput, onClick)
...
type Msg = Input String | Click
...
view : Model -> Html Msg
view model =
div []
[ input [ type_ "text", onInput Input ] []
, button [ onClick Click ] []
]
onInput : (String -> msg) -> Attribute msg
> Input : (String -> Msg)
update : msg -> model -> ( model, Cmd msg )
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Input str ->
( { model | typedValue = str }
, Cmd.none
)
Click ->
( model, WebSocket.sendMessage model.typedValue )
-- For multiple commands
Cmd.batch [ cmd1, cmd2 ]
view model =
...
input
[ type_ "text"
, onInput Input
, value model.typedValue
]
[]
update msg model =
...
Click ->
( { model | typedValue = "" }
, WebSocket.sendMessage model.typedValue
)
subscriptions : model -> Sub msg
type Msg =
...
| MessageReceived String
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ WebSocket.onMessage MessageReceived
]
onMessage : (String -> msg) -> Sub msg
-- Error:
-- This `case` does not have branches for all possibilities
--
-- Missing possibilities include:
-- `MessageReceived`
update msg model =
...
MessageReceived message ->
( { model | messages = message :: model.messages
}
, Cmd.none
)
(::) -- "cons" operator
-- adds to the beginning of the list
-- can be used for pattern matching
sum : List number -> number
sum list =
case list of
[] ->
0
head :: rest ->
head + sum rest
length : List a -> a
length list =
case list of
[] ->
0
_ :: rest ->
1 + length rest
view model =
let
viewMessage message =
li [] [ text message ]
in
div []
[ input
[ type_ "text"
, onInput Input
, value model.typedValue
]
[]
, button [ onClick Click ] [ text "Send" ]
, ul [] <| List.map viewMessage model.messages
]
$ elm make src/elm/Main.elm --output=build/elm.js
Create a custom HTML file
<body>
</body>
<script src="build/elm.js"></script>
<script>
Elm.Main.init({
node: document.querySelector('#elm'),
flags: {
currentTime: new Date().toLocaleTimeString()
}
});
</script>
-- Module has to be declared as port
port module WebSocket exposing (..)
-- elm -> js
port sendMessage : String -> Cmd msg
-- js -> elm
port onMessage : (String -> msg) -> Sub msg
const app = Elm.Main.init();
// elm -> js
app.ports.sendMessage.subscribe((message) => {
ws.send(message);
});
// js -> elm
app.ports.onMessage.send(message);
import Time -- elm install elm/time
import Task
type Msg
= Click
| NewTime Time.Posix
getNewTime : Cmd Msg
getNewTime =
Task.perform NewTime Time.now
import Browser.Dom as Dom
import Task
type Msg
= NoOp
jumpToBottom : String -> Cmd Msg
jumpToBottom id =
Dom.getViewportOf id
|> Task.andThen (\info -> Dom.setViewportOf id 0 info.scene.height)
|> Task.attempt (\_ -> NoOp)
and
import Json.Encode as JE
encode : Person -> JE.Value
encode person =
JE.object
[ ( "name", JE.string person.name )
, ( "age", JE.int person.age )
, ( "height", JE.float person.height )
]
module Json.Decode exposing (..)
import Json.Encode
type alias Value = Json.Encode.Value
import Json.Decode as JD
import Json.Decode.Pipeline as JDP
personDecoder : JD.Decoder Person
personDecoder =
JD.succeed Person
|> JDP.required "name" JD.int
|> JDP.optional "age" JD.string 20
|> JDP.hardcoded 1.8
decodeValue : Decoder a -> Value -> Result Error a
type Result error value
= Ok value
| Err error
res =
JD.decodeValue JD.string value
parseString =
case res of
Ok value ->
value
Err error
let
_ = Debug.log "Error" error
in
""
module Login exposing (..)
init = ...
view = ...
update = ...
subscriptions = ...
module Chat exposing (..)
init = ...
view = ...
update = ...
subscriptions = ...
module Main exposing (..)
import Login
import Chat
type Model
= LoginModel Login.Model
| ChatModel Chat.Model
type Msg
= LoginMsg Login.Msg
| ChatMsg Chat.Msg
main =
Browser.element ...
> LoginMsg : Login.Msg -> Msg
init : () -> ( Model, Cmd Msg )
init _ =
( LoginModel Login.init
, Cmd.none
)
view : Model -> Html Msg
view model =
case model of
LoginModel loginModel ->
Login.view loginModel -- Error
ChatModel chatModel ->
Chat.view chatModel -- Error
> Html Login.Msg -> Html Msg ???
> Html.map : (a -> b) -> Html a -> Html b
view : Model -> Html Msg
view model =
case model of
LoginModel loginModel ->
Login.view loginModel |> Html.map LoginMsg
ChatModel chatModel ->
Chat.view chatModel |> Html.map ChatMsg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case ( msg, model ) of
( LoginMsg loginMsg, LoginModel loginModel ) ->
handleLoginMsg loginMsg loginModel
( ChatMsg chatMsg, ChatModel chatModel ) ->
handleChatMsg chatMsg chatModel
_ ->
( model, Cmd.none )
handleLoginMsg : Login.Msg -> Login.Model -> ( Model, Cmd Msg )
handleLoginMsg loginMsg loginModel =
let
( updatedLoginModel, loginCmd ) =
Login.update loginMsg loginModel
in
( LoginModel updatedLoginModel
, Cmd.map LoginMsg loginCmd
)
handleChatMsg : Chat.Msg -> Chat.Model -> ( Model, Cmd Msg )
handleChatMsg chatMsg chatModel =
let
( updatedChatModel, chatCmd ) =
Chat.update chatMsg chatModel
in
( ChatModel updatedChatModel
, Cmd.map ChatMsg chatCmd
)
subscriptions : Model -> Sub Msg
subscriptions model =
case model of
LoginModel loginModel ->
Login.subscriptions loginModel |> Sub.map LoginMsg
ChatModel chatModel ->
Chat.subscriptions chatModel |> Sub.map ChatMsg