Elm Language

Ports (JS interop)

Syntax#

  • Elm (receiving): port functionName : (value -> msg) -> Sub msg
  • JS (sending): app.ports.functionName.send(value)
  • Elm (sending): port functionName : args -> Cmd msg
  • JS (receiving): app.ports.functionName.subscribe(function(args) { … });

Remarks#

Consult https://guide.elm-lang.org/interop/javascript.html from The Elm Guide to aid in understanding these examples.

Overview

A module, that is using Ports should have port keyword in it’s module definition.

port module Main exposing (..)

It is impossible to use ports with Html.App.beginnerProgram, since it does not allow using Subscriptions or Commands.

Ports are integrated in to update loop of Html.App.program or Html.App.programWithFlags.

Note

program and programWithFlags in elm 0.18 are inside the package Html instead of Html.App.

Outgoing

Outgoing ports are used as Commands, that you return from your update function.

Elm side

Define outgoing port:

port output : () -> Cmd msg

In this example we send an empty Tuple, just to trigger a subscription on the JavaScript side.

To do so, we have to apply output function with an empty Tuple as argument, to get a Command for sending the outgoing data from Elm.

update msg model =
    case msg of
        TriggerOutgoing data ->
            ( model, output () )

JavaScript side

Initialize the application:

var root = document.body;
var app = Elm.Main.embed(root);

Subscribe to a port with a corresponding name:

app.ports.output.subscribe(function () {
    alert('Outgoing message from Elm!');
});

Note

As of 0.17.0, immediate outgoing message to JavaScript from your initial state will have no effect.

init : ( Model, Cmd Msg )
init =
    ( Model 0, output () ) -- Nothing will happen

See the workaround in the example below.

Incoming

Incoming data from JavaScript is going through Subscriptions.

Elm side

First, we need to define an incoming port, using the following syntax:

port input : (Int -> msg) -> Sub msg

We can use Sub.batch if we have multiple subscriptions, this example will only contain one Subscription to input port

subscriptions : Model -> Sub Msg
subscriptions model =
    input Get

Then you have to pass the subscriptions to your Html.program:

main =
    Html.program
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

JavaScript side

Initialize the application:

var root = document.body;
var app = Elm.Main.embed(root);

Send the message to Elm:

var counter = 0;
        
document.body.addEventListener('click', function () {
    counter++;
    app.ports.input.send(counter);
});

Note

Please note, that as of 0.17.0 the immediate app.ports.input.send(counter); after app initialization will have no effect!

Pass all the required data for the start-up as Flags using Html.programWithFlags

Immediate outgoing message on start-up in 0.17.0

To send an immediate message with data to JavaScript, you have to trigger an action from your init.

init : ( Model, Cmd Msg )
init =
    ( Model 0, send SendOutgoing )


send : msg -> Cmd msg
send msg =
    Task.perform identity identity (Task.succeed msg)

Get started

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Trying out ports</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="elm.js"></script>
    <script>
    
      var node = document.getElementById('app');
      var app = Elm.Main.embed(node);
      
      // subscribe to messages from Elm
      app.ports.toJs.subscribe(function(messageFromElm) {
        alert(messageFromElm);
        // we could send something back by
        // app.ports.fromJs.send('Hey, got your message! Sincerely, JS');
      });
      
      // wait three seconds and then send a message from JS to Elm
      setTimeout(function () {
        app.ports.fromJs.send('Hello from JS');
      }, 3000);
      
    </script>
  </body>
</html>

Main.elm

port module Main exposing (..)

import Html

port toJs : String -> Cmd msg
port fromJs : (String -> msg) -> Sub msg

main =
   Html.program
        { init = (Nothing, Cmd.none) -- our model will be the latest message from JS (or Nothing for 'no message yet')
        , update = update
        , view = view
        , subscriptions = subscriptions
        }

type Msg
    = GotMessageFromJs String

update msg model =
    case msg of
        GotMessageFromJs message ->
            (Just message, toJs "Hello from Elm")

view model =
    case model of
        Nothing ->
            Html.text "No message from JS yet :("
        Just message ->
            Html.text ("Last message from JS: " ++ message)

subscriptions model =
    fromJs GotMessageFromJs

Install the elm-lang/html package if you haven’t yet by elm-package install elm-lang/html --yes.

Compile this code using elm-make Main.elm --yes --output elm.js so that the HTML file finds it.

If everything goes well, you should be able to open the index.html file with the “No message” text displayed. After three seconds the JS sends a message, Elm gets it, changes its model, sends a response, JS gets it and opens an alert.


This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow