If you have many ports, they could be all over your project, and therefore hard to manage and easy for forget about. Keep track of all of them by representing your outgoing ports in a union type like..
type JsMsg
= Download
| Login Login.Payload
| Logout
| Track Tracking.Payload
And sending them by
send : JsMsg -> Cmd msg
send jsMsg =
case jsMsg of
Download ->
toCmd
"Download"
Encode.null
-- ..
toCmd : String -> Encode.Value -> Cmd msg
toCmd type_ payload =
[ ( "type", Encode.string type_ )
, ( "payload", payload )
]
|> Encode.object
|> toJs
port toJs : Value -> Cmd msg
port fromJs : (Value -> msg) -> Sub msg
On the JS side of things you can listen for these messages like this
switch (msg.type) {
case "Download":
download();
break;
case "login user": {
ClientApi.login({
onSuccess: function(user) {
app.ports.fromJs.send({
type: "login succeeded",
payload: user
})
},
onFailure: function(err) {
app.ports.fromJs.send({
type: "login failed",
payload: String(err)
})
}
});
break;
// ..
Then to listen for these events, you need a Msg
decoder. This is kind of how your Msg.elm
file should look:
type Msg
= LoginFinished (Result String User)
| LogoutFinished (Result String ())
| DataFetched (Result String Data)
-- ..
| MsgDecodeFailed String
-- DECODER --
decode : Value -> Msg
decode json =
case Decode.decodeValue decoder json of
Ok msg ->
msg
Err err ->
MsgDecodeFailed err
decoder : Value -> Decoder Msg
decoder =
Decode.field "type" Decode.string
|> Decode.andThen
(Decode.field "payload" << toMsg)
toMsg : String -> Decoder Msg
toMsg type_ =
case type_ of
"login succeeded" ->
User.decoder
|> Decode.map (Ok >> LoginFinished)
"login failed" ->
Decode.string
|> Decode.map (Err >> LoginFinished)
"logout succeeded" ->
Decode.succeed (LogoutFinished (Ok ()))
"logout failed" ->
Decode.string
|> Decode.map (Err >> LogoutFinished)
"retreive data succeeded" ->
Data.decoder
|> Decode.map (Ok >> DataFetched)
"retreive data failed" ->
Decode.string
|> Decode.map (Err >> DataFetched)
_ ->
Decode.fail ("unrecognized msg type : " ++ type_)