2015-04-17 19 views
4

我正在Elm中做一个小应用程序。它在屏幕上显示一个计时器,当计时器达到零时,它会播放声音。我无法弄清楚如何从定时器发送消息(?)到声音播放器。发送来自elm子组件的信号

架构上,我有三个模块:Clock模块表示定时器,PlayAudio模块,可以播放音频和Main模块联系在一起的Clock模块和PlayAudio模块。

理想的情况下,当时钟到达零,我想要做的事就像从Clock模块发送信号。当时钟到达零,Clock将信号发送到Main,将其转发给PlayAudio

但是,从阅读榆树文档,好像比Main处理的信号是不鼓励的任何其他。所以这导致我的第一个问题。建立这种状态变化的好方法是什么? Clockupdate函数是否应返回是否已结束? (这是我如何做以下,但我会很开放,如何做的更好的建议。)

我的第二个问题是关于如何让播放声音。我将使用原始Javascript播放声音,我相信这意味着我必须使用端口。但是,我不确定如何与我的子模块PlayAudio中的Main中定义的端口进行交互。

以下是我正在使用的代码。

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> 

回答

1

这种方法看起来非常有原则性,并符合Elm Architecture post的指导原则。在最后的文档中有一个名为​​的部分,它的确如你所做的那样:如果需要从组件向另一个组件发送信号,那么更新函数会返回一对。
所以我认为你做对了。当然,在这样一个小应用程序中如此严格地遵循这种架构会增加样板/相关代码率。

总之,只有你需要做的更改是在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 is out,并且将至少是简化进口。但更重要的是,从Elm(无端口)与JavaScript进行互操作变得更加容易,因此只要有人创建了与声音库的绑定,就应该能够取消该端口。