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.
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
.
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.