Skip to content

Latest commit

 

History

History
executable file
·
137 lines (105 loc) · 4.83 KB

Ports.md

File metadata and controls

executable file
·
137 lines (105 loc) · 4.83 KB

Ports explained

Ports are functions, but with something special. Take a look at this code, which is from the Elm websockets demo.

port sendMessage : String -> Cmd msg
port messageReceiver : (String -> msg) -> Sub msg

There are two unusual things about these lines. The first is that they both start with the keyword port. The second is that these are function type definitions without any implementation. These two unusual things are related - the port prefix tells the Elm platform that it will provide the implementation.

That aside, sendMessage and messageReceiver are like any other Elm functions, and can be used in the same way.

Now we will look at outgoing and incoming ports in more detail. We will continue using the same example, with an application that defines a message of type Msg. Despite this specific type, ports need to be defined with a parameterised msg (lower case first letter). That's just the way they work.

Outgoing ports

When we update our model we typically have a function that looks like this:

update : Msg -> Model -> ( Model, Cmd Msg )

The output of the function is a pair: our updated model, and a command with any further instructions. The Cmd is what goes up the stack, out the platform and (usually) back in again.

When we want to send something out to JavaScript we use this exact mechanism. We send a Cmd out of the update function, up the stack, and Elm sends it out to JavaScript. To create the Cmd we use the port definition:

port sendMessage : String -> Cmd msg

This says that sendMessage will take a string and turn it into the appropriate command, and the Elm platform will then send the string to JavaScript. The Elm platform provides the implementation for sendMessage; we don't need to worry about it.

So this code will create a command that sends the model's draft field to JavaScript:

sendMessage model.draft

Once the string has passed out of the Elm platform we need some code on the JavaScript side to pick it up. That's what this code does:

app.ports.sendMessage.subscribe(function(message) {
    socket.send(message);
});

This says: "Take the (JavaScript side of the) Elm app, look at the sendMessage port, and subscribe to anything that comes out of it". What will come out is any string we send. That will be processed by the anonymous function, in a variable called message.

Incoming ports

We also want to capture messages on the JavaScript side and send them into our Elm app. In our websockets example we use this code on the JavaScript side:

socket.addEventListener("message", function(event) {
    app.ports.messageReceiver.send(event.data);
});

The important line is the second one. Given some data we want to send in, (event.data) it says: "Take the (JavaScript side of the) Elm app, look at the messageReceiver port, and send it in".

Now we need to pick it up on the Elm side.

In these cases we use a Sub Msg, which is a subscription to something that will send a Msg to our update function. Elm's ports will create the the subscription for us, but Elm needs some help. The problem is that the message coming in is a string, but our update function wants to process a Msg. Elm needs us to tell it how to convert a string into a Msg.

We can at least define our port, though:

port messageReceiver : (String -> msg) -> Sub msg

This says, "Make me a function called messageReceiver that, assuming I can tell you how to convert a string into some message, gives me a subscription that brings in messages from a port".

How can we convert strings into messages? We have the answer in our Msg definition:

type Msg
  = DraftChanged String
  | Send
  | Recv String

The particular kind of message we want to receive is a Recv string, and Recv itself is a function which takes a string and turns it into our Msg.

Now we can write our usual subscription function in our Elm app:

subscriptions : Model -> Sub Msg
subscriptions _ =
  messageReceiver Recv

This says, "Return a subscription generated by messageReceiver, and use Recv to convert an incoming string to a message.

That is the last piece of the jigsaw puzzle. Now we have the whole journey from some value from the JavaScript side to a message going into our update function on the Elm side.