ElmでプルダウンメニューのonChangeイベント

[Fringe81 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/fringe81)の22日目です。

Elmでの`onChange`イベントの捕捉の仕方を書いた日本語の記事を見つけられなかったので書きました。

以下が前提です。

- Elm 0.18
- `elm-lang/html/2.0.0/Html.Events`

# やりたいこと
プルダウンメニューの選択が変わった場合に何かやらせたい。

# やりかた

まず`onChange`関数を作る。

```Elm
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events as Events exposing (on)
import Json.Decode as Json

onChange : (String -> msg) -> Attribute msg
onChange handler =
    on "change" (Json.map handler Events.targetValue)
```

次に使い方

```Elm
type Msg
    = Change String


view : Model -> Html Msg
view model =
    let
        handler selectedValue =
            Change selectedValue
    in
    div []
        [ div [] [ text selectedText ]
    select [ onChange handler ]
        [ option [ value "a" ] [ text "a" ]
        , option [ value "b" ] [ text "b" ]
        , option [ value "c" ] [ text "c" ]
    ]
```

これでプルダウンメニューで変更が起きた時に`Change`メッセージが発生して、`selectedValue`には選択された`option`の`value`が入ってきます。

# 説明

ElmではHtmlイベントを捕捉して何かをやらすには [Html.Events](http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events) を使いますが、`onChange`関数は定義されていません。  

```Elm:Events.elm
module Html.Events exposing
  ( onClick, onDoubleClick
  , onMouseDown, onMouseUp
  , onMouseEnter, onMouseLeave
  , onMouseOver, onMouseOut
  , onInput, onCheck, onSubmit
  , onBlur, onFocus
  , on, onWithOptions, Options, defaultOptions
  , targetValue, targetChecked, keyCode
  )
```

ではどうすれば良いかというとことで、[Document](http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events)を見てみます。


> `on : String -> Decoder msg -> Attribute msg`

> Create a custom event listener. Normally this will not be necessary, but you have the power! Here is how onClick is defined for example:

`on`関数を使うと`custom event listener`を作れるみたいですね。


Documentには`onClick`を作る実装が書いてあります。
というかよーく見てみると`onClick`関数そのもの[実装](https://github.com/elm-lang/html/blob/2.0.0/src/Html/Events.elm#L48)でした。

```Elm
import Json.Decode as Json

onClick : msg -> Attribute msg
onClick message =
  on "click" (Json.succeed message)
```

その後には引数の話が書いてある。

> The first argument is the event name in the same format as with JavaScript's addEventListener function.
> 
> The second argument is a JSON decoder. Read more about these here. When an event occurs, the decoder tries to turn the event object into an Elm value. If successful, the value is routed to your update function. In the case of onClick we always just succeed with the given message.

第1引数は、JavaScriptの`addEventListener`を使う時の名前。
第2引数は、`JSON decoder`。

なので`onChange`関数を作る場合には、
第1引数に`"change"`、第2引数に`msg`に変換する関数を与えています。


ちなみに`JSON decoder`については下記の素晴らしい記事がありました。

- [[Elm] Decoder a からいろいろ理解ってしまおう](https://qiita.com/ymtszw/items/1cabbdbda4273b4c1978)

# まとめ
`on`関数は`HTML.Events`に定義されていないイベントも扱える便利な関数ですね。
今回説明したソースの完全版は[ここ](https://gist.github.com/asmasa/f8e0ed397bc6737b36c6f7a196397f5b)にあります。

ところで、プルダウンメニューって複数選択も可能でしたよね。。。
安心して下さい！Elm Packageで見つけました！

- [abadi199/elm-input-extra/5.2.1](http://package.elm-lang.org/packages/abadi199/elm-input-extra/5.2.1)

これを使うもよし、[実装](https://github.com/abadi199/elm-input-extra/blob/5.2.1/src/MultiSelect.elm)を参考に作るものよし！

# 参考
- [Building custom DOM event handlers in Elm](https://robots.thoughtbot.com/building-custom-dom-event-handlers-in-elm)
- [onChange event #23](https://github.com/elm-lang/html/issues/23)