Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Only have one outgoing port, and only one incoming port

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_)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.