2
votes

UPDATE : This has now been covered in the Elm Architecture documentation.

--

I don't understand how you tie the Elm Architecture and Tasks.

-- Action is an enumeration of possible actions
type Action = ..

-- Model is a Model that evolves in time
model : Signal Model

-- View turns a model into Html, potentially sending actions to an address
view : Address Action -> Model -> Html

-- Update turns a model and an action into another model
update : Action -> Model -> Model

-- Main waits for new values of the model to be emitted, and pass then to the view action
main : Signal Html
main =
  Signal.map (view actions.address) model

I'm trying to model this:

  • when a user click on a button, a "DoSomethingAction" is emitted
  • this action should start a Task, handled by some port
  • when the task is done, it should emit another Action ("SomethingDoneAction"), with the result

Is that the right way to go?

Which functions should I change (update, main)?

I understand this is what is alluded to here, but the explanation is not too clear and I don't understand what needs to be changed.

So any complete example / or more explanation would be welcome.

EDIT :

A more complete version (that "kinda" works) is available here : http://share-elm.com/sprout/55bf3c3ce4b06aacf0e8ba17

I'm a few steps from having it working, it seems, since the Task is not always run when I click on the button (but hopefully I'm getting somewhere.)

3
did you finally find a proper way to achieve this? - jarandaf
@jarandaf : evancz covered it in the "Elm architecture" doc, I suggest you give it another look. Have fun ! - phtrivier
thank you! Missed the Effects API bit - jarandaf

3 Answers

2
votes

While it is very possible to perform Tasks when a button is pressed, it is often not needed. What you probably want is to send a message to the Action signal, then with that update the Model, then with that the view may change. This is standard Elm architecture.

If you really want to perform Tasks, you could do the following:

type Action = NoOp | ButtonClicked | SetText String

type alias Model =
  { tasks : Task Http.Error String
  , text : String
  }

init : Model
init =
  { task = Task.succeed "Fetching..."
  , text = ""
  }

-- We can use, for example, the Http.get task
update : Action -> Model -> Model
update action model =
  case action of
    ButtonClicked ->
      { model | task <-
        Http.getString "http://example.org/some-text.txt"}
    SetText t ->
      { model | text <- t }
    _ -> model

view : Address Action -> Model -> Html
view address model =
  div []
   [ div
      [ class "btn btn-default"
      , onClick address ButtonClicked
      ]
      [ text "Click me!" ]
   , h3 "Here is what was fetched:"
   , p [] [ text model.text ]
   ]

-- now possibly in another file

actions : Signal.Mailbox Action
actions : Signal.mailbox NoOp    

model : Signal Model
model = Signal.foldp init update actions.signal

-- This is what actually performs the Tasks
-- The Elm task example also details how to send
port taskPort : Signal (Task Http.Error String)
port taskPort =
  ((.task) <~ model) `andThen`
    (Signal.send actions.address << SetText)

main : Signal Html
main = view actions.address <~ model

Note that you can use Signal.dropRepeats if you want to perform the task only when the task changes.

0
votes

The way to do it is to have the update of the model mapped to a response signal that receive the results of what you want your model to react to.

The view sends the tasks to a query/requests mailbox.

The port would be a map on the query signal that executes the tasks andThen sends the results to the response mailbox.

If you want to see this in action take a look at this file (I've highlighted the relevant parts)

https://github.com/pdamoc/elmChallenges/blob/master/challenge4.elm#L72-L94

Please be advised that what you see there is a little bit more complicated than what you need because it also implements a mechanism to execute the tasks only when the user stops typing.