7.2

Sending Data to JavaScript

In the Pure Functions section, we learned that JavaScript functions can cause side effects. Elm functions on the other hand are pure and as a result don’t cause any side effects. As nice as it is to have pure functions that are highly reliable, to do anything useful Elm programs must deal with the outside world which is riddled with side effects.

In the Elm Runtime to the Rescue section, we learned how Elm uses commands, subscriptions, and messages to properly manage side effects originated from interacting with the outside world.

The figure above shows that the way we interact with a JavaScript library is very similar to how we interact with an external service such as an HTTP server. In both cases we tell the Elm runtime to perform an operation by sending it a command. When the operation is complete, the runtime sends a message back to our app.

To further understand this interaction, let’s create a simple Elm app that sends and receives data from JavaScript code. Create a file called PortExamples.elm inside a new directory called Ports in beginning-elm.

Add the following code to PortExamples.elm.

module PortExamples exposing (..)

import Html exposing (..)
import Html.Events exposing (..)


type alias Model =
    String


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick SendDataToJS ]
            [ text "Send Data to JavaScript" ]
        ]


type Msg
    = SendDataToJS


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        SendDataToJS ->
            ( model, Cmd.none )


init : ( Model, Cmd Msg )
init =
    ( "", Cmd.none )


main : Program Never Model Msg
main =
    Html.program
        { init = init
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        }

The code above is quite simple. All it does is display a button titled Send Data to JavaScript. When that button is clicked, update receives the SendDataToJS message. To send data to JavaScript, we need to create a command inside the SendDataToJS -> branch in update.

Up until now we’ve been relying on specific functions such as Random.generate, Http.send, and RemoteData.sendRequest to create a command.

Random.generate : (a -> msg) -> Generator a -> Cmd msg

Http.send : (Result Error a -> msg) -> Request a -> Cmd msg

RemoteData.sendRequest : Request a -> Cmd (WebData a)

To create a command for sending data to JavaScript we’ll have to use a different approach that involves a port. Let’s define a function called sendData right above the update function.

port sendData : String -> Cmd msg

Wait a minute. That doesn’t look like a function. Where is the function body? And what is the port keyword doing in front of the function name? The diagram below answers those questions.

There is something odd about sendData’s return type. If you compare its definition with the ones listed above for Random.generate, Http.send, and RemoteData.sendRequest, you’ll notice that the type variable msg appears out of nowhere in sendData’s definition. Usually, if a type variable is included in a return type we can trace its origin back to a parameter, but that’s not the case here. sendData’s parameter is a simple String with no type variable.

What’s really going on is that unlike all other commands we’ve seen so far, the command generated by a port function doesn’t send a message back to the update function once the operation is complete. If you think about it, it actually makes sense not to send any messages back to our app. All we want to do is tell the Elm runtime to send some data to JavaScript. We’re not concerned with whether that data is indeed sent to JavaScript or not.

A command that doesn’t send any messages back to the app always has the type Cmd msg. If we had used Cmd Msg as the return type instead, we would be implying that the command sends a message of type Msg which is not true.

If you look inside the Platform.Cmd module, you’ll notice that the Cmd.none value we’ve been using throughout this book to represent the absence of a command also uses Cmd msg as the return type.

Cmd.none : Cmd msg

The difference between Cmd.none and the sendData port function is that the former doesn’t create any command, but the latter creates a command that doesn’t send any messages back to the app.

Now that we know what sendData is and what it does, let’s use it in the update function to create a command.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        SendDataToJS ->
            ( model, sendData "Hello JavaScript!" )

The syntax for applying a port function is identical to that of a regular function. When the update function receives the SendDataToJs message, it’ll use sendData to create a command that sends the string "Hello JavaScript" to, you guessed it, JavaScript.

Next we need to compile the Elm code in PortExamples.elm to JavaScript. Run the following command from the beginning-elm directory in terminal.

$ elm-make Ports/PortExamples.elm --output Ports/elm.js

Unfortunately, the Elm compiler throws an error.

------------------------ BAD PORT -------------------- Ports/PortExamples.elm
You are declaring port `sendData` in a normal module.

23| port sendData : String -> Cmd msg
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
All ports must be defined in a `port module`. You should probably have just one
of these for your project. This way all of your foreign interactions stay
relatively organized.

Detected errors in 1 module.

It’s saying that we should declare sendData in a port module instead of a normal module. What exactly is a port module? It’s a module whose declaration is prefixed with the keyword port. Let’s make PortExamples a port module by modifying the first line in PortExamples.elm to this:

port module PortExamples exposing (..)

Run the following command one more time from the beginning-elm directory in terminal.

$ elm-make Ports/PortExamples.elm --output Ports/elm.js

The error should go away and you should see a file called elm.js in the beginning-elm/Ports directory. Now we need to write some JavaScript code to receive the string sent by the Elm app on the other end. Let’s create a new file called index.html in the beginning-elm/Ports directory and add the code below to it.

<!DOCTYPE html>
<html>
<body>
    <div id="elm-code-is-loaded-here"></div>
    <script src="elm.js"></script>
    <script>
        var element = document.getElementById("elm-code-is-loaded-here");
        var app = Elm.PortExamples.embed(element);

        app.ports.sendData.subscribe(function(data) {
            console.log("Data from Elm: ", data);
        });
    </script>
</body>
</html>

The first thing we did in index.html is create a div that will load the Elm app. We then included the elm.js file which contains the compiled code. Finally, the last <script> section is where the code for receiving the string sent by our Elm app goes. Let’s go through that code step by step.

Step 1: Get hold of the div with id elm-code-is-loaded-here.

var element = document.getElementById("elm-code-is-loaded-here");

Step 2: Embed the Elm app contained in the PortExamples module into the div from step 1.

var app = Elm.PortExamples.embed(element);

Step 3: Listen to the sendData port using the subscribe method. When the data arrives, print it to the browser console.

app.ports.sendData.subscribe(function(data) {
    console.log("Data from Elm: ", data);
});

All the ports defined in our Elm app can be accessed through app.ports. Finally, we’re ready to test. Open the Ports/index.html file in a browser. After that open the browser console. Now if you click the Send Data to JavaScript button, you should see Data from Elm: Hello JavaScript! in the console.

The following diagram illustrates the workflow we just implemented for sending data to JavaScript.

Summary

In this section, we learned how to send data to JavaScript from an Elm app using a port. Elm treats JavaScript just like an HTTP server. That’s why we need to use a command to send data to JavaScript too. Elm doesn’t allow any code to pass through a port. All we can send is data. In the next section we’ll learn how to receive data from JavaScript.

Here is the entire code from PortExamples.elm for your reference:

port module PortExamples exposing (..)

import Html exposing (..)
import Html.Events exposing (..)


type alias Model =
    String


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick SendDataToJS ]
            [ text "Send Data to JavaScript" ]
        ]


type Msg
    = SendDataToJS


port sendData : String -> Cmd msg


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        SendDataToJS ->
            ( model, sendData "Hello JavaScript!" )


init : ( Model, Cmd Msg )
init =
    ( "", Cmd.none )


main : Program Never Model Msg
main =
    Html.program
        { init = init
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        }
Back to top

New chapters are coming soon!

Sign up for the Elm Programming newsletter to get notified!

* indicates required
Close