I am making a small application in Elm. It displays a timer on the screen, and when the timer reaches zero, it plays a sound. It's hard for me to understand how to send a message (?) From a timer to a sound player.
Architecturally, I have three modules: a module Clockthat represents a timer, a module PlayAudiothat can play audio, and a module Mainthat binds the module Clockand PlayAudio.
Ideally, when the clock reaches zero, I want to do something like sending a signal from a module Clock. When the clock reaches zero, it will send a signal to , which will send it to . ClockMainPlayAudio
However, after reading the Elm documentation, it seems that you have something different Main. So this leads me to my first question. What is a good way to model this state change? If the function updateof the Clockreturn, over or not? (This is how I do it below, but I would be very open to suggestions on how to do it better.)
My second question is how to make the sound play. I will use raw Javascript to play sound, which I believe means that I have to use ports. However, I am not sure how to communicate with the port defined in Main, from my submodulePlayAudio .
Below is the code I'm using.
Clock.elm:
module Clock (Model, init, Action, signal, update, view) where
import Html (..)
import Html.Attributes (..)
import Html.Events (..)
import LocalChannel (..)
import Signal
import Time (..)
type ClockState = Running | Ended
type alias Model =
{ time: Time
, state: ClockState
}
init : Time -> Model
init initialTime =
{ time = initialTime
, state = Running
}
type Action = Tick Time
update : Action -> Model -> (Model, Bool)
update action model =
case action of
Tick tickTime ->
let hasEnded = model.time <= 1
newModel = { model | time <-
if hasEnded then 0 else model.time - tickTime
, state <-
if hasEnded then Ended else Running }
in (newModel, hasEnded)
view : Model -> Html
view model =
div []
[ (toString model.time ++ toString model.state) |> text ]
signal : Signal Action
signal = Signal.map (always (1 * second) >> Tick) (every second)
PlaySound.elm:
module PlaySound (Model, init, update, view) where
import Html (..)
import Html.Attributes (..)
import Html.Events (..)
import LocalChannel (..)
import Signal
import Time (..)
type alias Model =
{ playing: Bool
}
init : Model
init =
{ playing = False
}
update : Bool -> Model -> Model
update shouldPlay model =
{ model | playing <- shouldPlay }
view : Model -> Html
view model =
let node = if model.playing
then audio [ src "sounds/bell.wav"
, id "audiotag" ]
[]
else text "Not Playing"
in div [] [node]
Main.elm:
module Main where
import Debug (..)
import Html (..)
import Html.Attributes (..)
import Html.Events (..)
import Html.Lazy (lazy, lazy2)
import Json.Decode as Json
import List
import LocalChannel as LC
import Maybe
import Signal
import String
import Time (..)
import Window
import Clock
import PlaySound
---- MODEL ----
-- The full application state of our todo app.
type alias Model =
{ clock : Clock.Model
, player : PlaySound.Model
}
emptyModel : Model
emptyModel =
{ clock = 10 * second |> Clock.init
, player = PlaySound.init
}
---- UPDATE ----
type Action
= NoOp
| ClockAction Clock.Action
-- How we update our Model on a given Action?
update : Action -> Model -> Model
update action model =
case action of
NoOp -> model
ClockAction clockAction ->
let (newClock, hasEnded) = Clock.update clockAction model.clock
newPlaySound = PlaySound.update hasEnded model.player
in { model | clock <- newClock
, player <- newPlaySound }
---- VIEW ----
view : Model -> Html
view model =
let context = Clock.Context (LC.create ClockAction actionChannel)
in div [ ]
[ Clock.view context model.clock
, PlaySound.view model.player
]
---- INPUTS ----
-- wire the entire application together
main : Signal Html
main = Signal.map view model
-- manage the model of our application over time
model : Signal Model
model = Signal.foldp update initialModel allSignals
allSignals : Signal Action
allSignals = Signal.mergeMany
[ Signal.map ClockAction Clock.signal
, Signal.subscribe actionChannel
]
initialModel : Model
initialModel = emptyModel
-- updates from user input
actionChannel : Signal.Channel Action
actionChannel = Signal.channel NoOp
port playSound : Signal ()
port playSound = ???
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="js/elm.js" type="text/javascript"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<script type="text/javascript">
var todomvc = Elm.fullscreen(Elm.Main);
todomvc.ports.playSound.subscribe(function() {
setTimeout(function() {
document.getElementById('audiotag').play();
}, 50);
});
</script>
</body>
</html>