Search code examples
elm

In Elm, how can I listen to mousewheel events generated on a node


See the following example on Ellie:

module Main exposing (main)

import Html exposing (..)
import Html.Attributes exposing (style)
import Html.Events exposing (Options, onClick, onWithOptions)
import Json.Decode as JD
import Json.Decode.Extra exposing ((|:))
import Result exposing (withDefault)
import String as S


main : Program Never Model Msg
main =
    Html.program
        { init = init
        , update = update
        , subscriptions = subscriptions
        , view = view
        }


init : ( Model, Cmd Msg )
init =
    ( Model "", Cmd.none )


type alias Model =
    { message : String
    }


type Msg
    = Zoom MouseWheelEvent
    | Clicked


type alias MouseWheelEvent =
    { deltaX : Float
    , deltaY : Float
    , clientX : Float
    , clientY : Float
    }



update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Zoom e ->
            ( { model | message = toString e }, Cmd.none )
        Clicked ->
            ( { model | message = "Clicked!" }, Cmd.none )


decodeMouseEvent : JD.Decoder Msg
decodeMouseEvent =
    JD.map Zoom
        (JD.succeed MouseWheelEvent
            |: mouseAttribute "deltaX"
            |: mouseAttribute "deltaX"
            |: mouseAttribute "deltaX"
            |: mouseAttribute "deltaX"
        )


mouseAttribute : String -> JD.Decoder Float
mouseAttribute k =
    JD.at [ k ] JD.string |> JD.andThen readFloat


readFloat : String -> JD.Decoder Float
readFloat n =
    JD.succeed (withDefault 0 (S.toFloat n))


view : Model -> Html Msg
view m =
    div []
        [ p [] [ text <| "message: " ++ m.message ]
        , div
            [ onWithOptions "mousewheel" (Options True True) decodeMouseEvent
            , onClick Clicked
            , style
                [ ( "height", "250px" )
                , ( "width", "250px" )
                , ( "background-color", "green" )
                ]
            ]
            [ p [] [ text "Zoom here" ]
            ]
        ]


subscriptions m =
    Sub.none

I want to capture the mousewheel event on a specific element. This is to do with creating a zoom effect on an SVG image.

As per the example, if I attach a listener to a specific element then the update function does not get called. The event appears to be captured by the document body and does not reach my target element. So how do I go about getting my event handler to trigger?

I appreciate it might be possible to do this with ports, but it seems wrong to grab something which is occurring at the document.body level, when I am only interested in capturing it when it occurs over a specific element.

Is it the case that mousewheel is only ever viewed as a document level event?


Solution

  • The event is being triggered but your decoder is failing because the fields event.deltaX, event.deltaY, etc are not strings but floats. AFAIK Elm silently ignores the event if the decoder fails. It works if I change the decoder from:

    decodeMouseEvent : JD.Decoder Msg
    decodeMouseEvent =
        JD.map Zoom
            (JD.succeed MouseWheelEvent
                |: mouseAttribute "deltaX"
                |: mouseAttribute "deltaX"
                |: mouseAttribute "deltaX"
                |: mouseAttribute "deltaX"
            )
    
    
    mouseAttribute : String -> JD.Decoder Float
    mouseAttribute k =
        JD.at [ k ] JD.string |> JD.andThen readFloat
    
    
    readFloat : String -> JD.Decoder Float
    readFloat n =
        JD.succeed (withDefault 0 (S.toFloat n))
    

    to:

    decodeMouseEvent : JD.Decoder Msg
    decodeMouseEvent =
        JD.map Zoom
            (JD.succeed MouseWheelEvent
                |: JD.field "deltaX" JD.float
                |: JD.field "deltaY" JD.float
                |: JD.field "clientX" JD.float
                |: JD.field "clientY" JD.float
            )