Protecting Boundaries

# Protecting Boundaries between Elm and JavaScript

What happens if we send a number instead of a string from JavaScript to our Elm app? page

protecting-boundaries

<!DOCTYPE html> <html> <body> <div id="elm-code-is-loaded-here"></div> <script src="elm.js"></script> <script> var app = Elm.PortExamples.init({ node: document.getElementById("elm-code-is-loaded-here") }); app.ports.sendData.subscribe(function(data) { console.log("Data from Elm: ", JSON.stringify(data)); app.ports.receiveData.send("Hey Elm!"); }); </script> </body> </html>

https://wiki.ralfbarkow.ch/assets/protecting-boundaries/index.html

Click the `Send Data to JavaScript` button. Open the browser console.

Data from Elm: {"comments":[{"body":"some comment","id":1,"postId":1}],"posts":[{"author":{"name":"typicode","url":"https://github.com/typicode"},"id":1,"title":"json-server"},{"author":{"name":"indexzero","url":"https://github.com/indexzero"},"id":2,"title":"http-server"}],"profile":{"name":"typicode"}}

PortExamples.elm

port module PortExamples exposing (main) import Browser import Html exposing (..) import Html.Events exposing (onClick) import Json.Decode exposing (Error(..), Value, decodeValue, string) type alias ComplexData = { posts : List Post , comments : List Comment , profile : Profile } type alias Post = { id : Int , title : String , author : Author } type alias Author = { name : String , url : String } type alias Comment = { id : Int , body : String , postId : Int } type alias Profile = { name : String } type alias Model = { dataFromJS : String , dataToJS : ComplexData , jsonError : Maybe Error } view : Model -> Html Msg view model = div [] [ button [ onClick SendDataToJS ] [ text "Send Data to JavaScript" ] , viewDataFromJSOrError model ] viewDataFromJSOrError : Model -> Html Msg viewDataFromJSOrError model = case model.jsonError of Just error -> viewError error Nothing -> viewDataFromJS model.dataFromJS viewError : Error -> Html Msg viewError jsonError = let errorHeading = "Couldn't receive data from JavaScript" errorMessage = case jsonError of Failure message _ -> message _ -> "Error: Invalid JSON" in div [] [ h3 [] [ text errorHeading ] , text ("Error: " ++ errorMessage) ] viewDataFromJS : String -> Html msg viewDataFromJS data = div [] [ br [] [] , strong [] [ text "Data received from JavaScript: " ] , text data ] type Msg = SendDataToJS | ReceivedDataFromJS Value update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of SendDataToJS -> ( model, sendData model.dataToJS ) ReceivedDataFromJS value -> case decodeValue string value of Ok data -> ( { model | dataFromJS = data }, Cmd.none ) Err error -> ( { model | jsonError = Just error }, Cmd.none ) subscriptions : Model -> Sub Msg subscriptions _ = receiveData ReceivedDataFromJS port sendData : ComplexData -> Cmd msg port receiveData : (Value -> msg) -> Sub msg init : () -> ( Model, Cmd Msg ) init _ = ( initialModel, Cmd.none ) initialModel : Model initialModel = { dataFromJS = "" , dataToJS = complexData , jsonError = Nothing } complexData : ComplexData complexData = let post1 = Author "typicode" "https://github.com/typicode" |> Post 1 "json-server" post2 = Author "indexzero" "https://github.com/indexzero" |> Post 2 "http-server" in { posts = [ post1, post2 ] , comments = [ Comment 1 "some comment" 1 ] , profile = { name = "typicode" } } main : Program () Model Msg main = Browser.element { init = init , view = view , update = update , subscriptions = subscriptions }

make

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