エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Elm で mouse のイベントを取得する覚書

Elm のイベント用の関数は onClick など主だったものはライブラリで用意されていますが、それ以外のイベントは on 関数を利用して自分で合成する必要があります。

以下、合成方法の覚書です。

mousemove イベントをハンドリングする

イベントの値を受け取る型を用意します。

type Msg = Move Int Int

イベントのデコーダを用意します。

mousemove イベントのうち clientXclientY の二つのフィールドをそれぞれ Int として取得し、Move を適用するデコーダです。

map2 Move (field "clientX" int) (field "clientY" int)

map2, field, intJson.Decode で定義されている関数です。

動作を REPL で確認してみます。

$ elm repl
---- Elm 0.19.0 ----------------------------------------------------------------
Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
--------------------------------------------------------------------------------
> import Json.Decode exposing(map2, field, int, decodeString)
> type Msg = Move Int Int
> decodeString (map2 Move (field "clientX" int) (field "clientY" int)) "{\"clientX\":10,\"clientY\":20}"
Ok (Move 10 20)
    : Result Json.Decode.Error Msg
> 

{"clientX":10,"clientY":20} という JSON から Move 10 20 という値を取得することができました。

このデコーダon 関数に与えます。

イベントの構造については MDN などを参照してください。

実装

div 要素上のマウスカーソルの位置を表示するだけのサンプルです。

import Browser
import Html exposing (Html, div, span, text)
import Html.Events exposing (on)
import Html.Attributes exposing (style)
import Json.Decode exposing (map2, field, int)

main =
  Browser.sandbox
    { init = init
    , update = update
    , view = view
    }

type alias Model = { x: Int , y: Int }

init : Model
init = { x = 0 , y = 0 }

type Msg = Move Int Int

update : Msg -> Model -> Model
update msg model =
  case msg of
    Move x y -> {x = x, y = y}

view : Model -> Html Msg
view model =
  div []
    [ span
        []
        [ text ("(" ++ (String.fromInt model.x) ++ ", " ++ String.fromInt model.y ++ ")") ]
    , div
        [ style "background-color" "gray"
        , style "height" "80vh"
        , on "mousemove" (map2 Move (field "clientX" int) (field "clientY" int))
        ]
        []
    ]

合成する関数に名前をつける

見通しをよくするために名前をつけます。ここでは Move を引数で受け取るようにすることで他のメッセージにも利用できるようにしています。

-- view 以外は上と同じ

view : Model -> Html Msg
view model =
  div []
    [ span
        []
        [ text ("(" ++ (String.fromInt model.x) ++ ", " ++ String.fromInt model.y ++ ")") ]
    , div
        [ style "background-color" "gray"
        , style "height" "80vh"
        , onMouseMove Move
        ]
        []
    ]

onMouseMove : (Int -> Int -> msg) -> Html.Attribute msg
onMouseMove f =
  on "mousemove" (map2 f (field "clientX" int) (field "clientY" int))