2015-04-17 3 views
4

Я делаю небольшое приложение в Вяз. Он отображает таймер на экране, и когда таймер достигает нуля, он воспроизводит звук. Мне трудно понять, как отправить сообщение (?) От таймера к звуковому проигрывателю.отправка сигнала из подкомпонента в вязе

Архитектурно, у меня есть три модуля: а Clock модуль, который представляет собой таймер, модуль PlayAudio, который может воспроизводить аудио, и Main модуля, который связывает вместе Clock модуля и модуля PlayAudio.

В идеале, когда часы достигают нуля, я хочу сделать что-то вроде отправки сигнала от модуля Clock. Когда часы достигнут нуля, Clock отправит сигнал на Main, который переведет его на PlayAudio.

Однако, прочитав документацию Elm, похоже, что сделка с сигналами не имеет ничего, кроме Main. Итак, это приводит меня к моему первому вопросу. Каков хороший способ моделирования этого изменения состояния? Должна ли функция update от Clock вернуть, закончилось ли это? (Вот как я делаю это ниже, но я был бы очень открыт для предложений о том, как сделать это лучше.)

Мой второй вопрос - как получить звук для воспроизведения. Я буду использовать сырой Javascript для воспроизведения звука, который, я считаю, означает, что я должен использовать порты. Тем не менее, я не уверен , как взаимодействовать с портом, определенным в Main из моего подмодуля, PlayAudio.

Ниже приведен код, который я использую.

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. В этом документе в конце находится раздел под названием One last pattern, который делает именно то, что вы делаете: если функция обновления возвращает пару, если вам нужно передать сигнал от вашего компонента другому компоненту.
Итак, я думаю, вы делаете это правильно. Разумеется, следуя этой архитектуре, так строго в таком небольшом приложении, увеличивается коэффициент соответствия шаблону/соответствующему коду.

В любом случае, только изменения, которые вы должны внести, находятся в 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, и по крайней мере упростить импорт. Но более важно взаимодействие с JavaScript изнутри Elm (без портов) упрощается, поэтому, как только кто-то создает привязки к звуковой библиотеке, вы сможете уйти с этим портом.

Смежные вопросы