elm 0.19 で JSONを使ってjsと通信したメモ

## 概要

[前回](https://qiita.com/hibohiboo/items/8d893d4995be0a1dc743)は入力と表示を同じファイルにしていたが、分けてみる。

## ファイル

* `subscribe`で、 Elm -> javascript
* `send`で、javascript -> Elm

```js:app/src/card.js
'use strict';

require("./styles.scss");

const handouts = require('./Card/Main');
const creator = require('./Card/HandoutCreator');

const handoutsApp = handouts.Elm.Main.init({flags: 0, node: document.getElementById('cards')});
const creatorApp = creator.Elm.Main.init({flags: null, node: document.getElementById('cardCreator')});

creatorApp.ports.toJs.subscribe(data => {
  const json = JSON.stringify(data);
  handoutsApp.ports.fromJs.send(json);
})
```

* `port module`でportを使うモジュールを明示
* `subscriptions `にメッセージを登録
* `update`でjsonをモデルにデコード


```elm:app/src/Card/Main.elm
port module Main exposing (main)

import Browser
import Card.HandoutList as HandoutList
import Card.InputModel
import Html exposing (..)
import Html.Attributes exposing (..)
import Json.Decode



-- In ports


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


main : Program Int Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.batch [ fromJs FromJs ]
        }



-- model


type alias Model =
    { inputModel : Card.InputModel.Model
    , handoutListModel : HandoutList.Model
    }


initialModel : Model
initialModel =
    { inputModel = Card.InputModel.initialModel
    , handoutListModel = HandoutList.initialModel
    }


init : Int -> ( Model, Cmd Msg )
init flags =
    ( initialModel, Cmd.none )


type Msg
    = HandoutListMsg HandoutList.Msg
    | FromJs String



-- update


update : Msg -> Model -> ( Model, Cmd Msg )
update message model =
    case message of
        HandoutListMsg subMsg ->
            let
                ( updatedHandoutListModel, handoutListCmd ) =
                    HandoutList.update subMsg model.inputModel.title model.handoutListModel
            in
            ( { model | handoutListModel = updatedHandoutListModel }, Cmd.map HandoutListMsg handoutListCmd )

        FromJs json ->
            let
                r =
                    Json.Decode.decodeString Card.InputModel.decoder json

                -- decodeに成功したら、InputModelを。失敗したら元の値を返す。
                im =
                    case r of
                        Ok m ->
                            m

                        Err _ ->
                            model.inputModel
            in
            ( { model | inputModel = im }, Cmd.none )



-- subscription


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none



-- view


view : Model -> Html Msg
view model =
    div [ class "container" ]
        [ Html.map HandoutListMsg (HandoutList.view model.handoutListModel)
        ]

```

* jsonにエンコードしてjavascriptに送信


```elm:app/src/Card/HandoutCreator.elm
port module Main exposing (main)

import Browser
import Card.InputModel exposing (Model, encode)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Json.Encode


port toJs : Json.Encode.Value -> Cmd msg


main : Program (Maybe String) Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }


init : Maybe String -> ( Model, Cmd Msg )
init flags =
    ( Card.InputModel.initialModel, Cmd.none )


type Msg
    = NoOp
    | UpdateInput String



-- update


update : Msg -> Model -> ( Model, Cmd Msg )
update message model =
    case message of
        NoOp ->
            Tuple.pair model Cmd.none

        UpdateInput s ->
            let
                newModel =
                    { model | title = s }
            in
            Tuple.pair newModel (toJs (encode newModel))



-- view


view : Model -> Html Msg
view model =
    div []
        [ handoutInput model
        ]


handoutInput : Model -> Html Msg
handoutInput model =
    Html.form []
        [ label [ attribute "for" "inputTitle" ]
            [ text "タイトル"
            ]
        , input [ attribute "type" "text", id "inputTitle", class "browser-default", onInput UpdateInput, value model.title ] []
        ]

```

* デコーダーとエンコードの定義

```elm:app/src/Card/InputModel.elm
module Card.InputModel exposing (Item, Model, decoder, encode, initialModel)

import Json.Decode as D
import Json.Encode as E


type alias Item =
    String


type alias Model =
    { title : Item
    }


initialModel : Model
initialModel =
    { title = ""
    }


encode : Model -> E.Value
encode m =
    E.object
        [ ( "title", E.string m.title )
        ]


decoder : D.Decoder Model
decoder =
    D.map Model
        (D.field "title" D.string)
```

## 参考
[ElmとJavaScriptの会話(Port)][*1]
[elm/json][*2]
[result][*3]

[*1]:https://qiita.com/sand/items/eeabb2220b2ed6fc0fea
[*2]:https://github.com/elm/json
[*3]:https://guide.elm-lang.org/v/a4b34b537117aa92fd8eba28cbdb3c93454f28db/error_handling/result.html