Sending a signal from a subcomponent in elm

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 (..)

-- MODEL

type ClockState = Running | Ended

type alias Model =
    { time: Time
    , state: ClockState
    }

init : Time -> Model
init initialTime =
    { time = initialTime
    , state = Running
    }

-- UPDATE

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

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 (..)

-- MODEL

type alias Model =
    { playing: Bool
    }

init : Model
init =
    { playing = False
    }

-- UPDATE

update : Bool -> Model -> Model
update shouldPlay model =
    { model | playing <- shouldPlay }

-- VIEW

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>
+4
1

Post Elm Architecture. , , : , .
, . , , , / .

, , , Main.elm. Channel Main, Main , . , .

---- UPDATE ----

-- How we update our Model on a given Action?
update : Clock.Action -> Model -> (Model, Bool)
update clockAction model =
    let (newClock, hasEnded) = Clock.update clockAction model.clock  
        newPlaySound = PlaySound.update hasEnded model.player
    in ( { model | clock <- newClock
               , player <- newPlaySound }, hasEnded)

---- VIEW ----

view : Model -> Html
view model =
    div [ ]
      [ Clock.view model.clock
      , PlaySound.view model.player
      ]

---- INPUTS ----

-- wire the entire application together
main : Signal Html
main = Signal.map (view << fst) model

-- manage the model of our application over time
model : Signal Model
model = Signal.foldp update initialModel Clock.signal

initialModel : Model
initialModel = emptyModel

port playSound : Signal ()
port playSound =
  model
  |> Signal.map snd
  |> Signal.keepIf ((==) True)
  |> Signal.map (always ())

: Elm 0.15 , , . JavaScript Elm ( ) , , - , .

+1

All Articles