At Zaptic, we allow our customers to create a flow chart describing the business processes that they would like to complete. We draw the flow chart in SVG using a pan & zoom interface to make it easy to look around. We track mouse down, mouse drag & mouse up events to manage this UI.
Unfortunately, in development, the experience was a little annoying as every time we loaded the browser's inspector on an element the mousedown
of the right-click would be tracked by the UI but the mouseup
would be over the context menu and so would not register. The result? The UI gets left in "pan" mode and attempts to follow your mouse everywhere even though you don't have any mouse buttons pressed any more.
To overcome this, we needed to look into tracking which mouse button was being used so that we could ignore the right-clicks. We are using the elm-lang/mouse package which includes a position
decoder that can be used for mousedown
events. That decoder only provides the mouse location as pageX
and pageY
, it doesn't provide any information about which button was pressed so we need to go a little deeper.
If we take a look at the source code for the module we can see that the position decoder is doing the following:
position : Json.Decoder Position
position =
Json.map2 Position
(Json.field "pageX" Json.int)
(Json.field "pageY" Json.int)
It is looking for the pageX
and pageY
properties, as we suspected, and it expects them to be integers. If we look at the lovely MDN documentation for a mousedown event we can see that it has lots of other properties including a integer button
property which contains some indication of which button is being used. We can set up our own type & decoder as:
type alias MouseClick =
{ pageX : Int
, pageY : Int
, button : Int
}
mouseClickDecoder : Json.Decode.Decoder PFM.MouseClick
mouseClickDecoder =
Json.Decode.map3 PFM.MouseClick
(Json.Decode.field "pageX" Json.Decode.int)
(Json.Decode.field "pageY" Json.Decode.int)
(Json.Decode.field "button" Json.Decode.int)
And adapt our event handler to use it:
type Msg
= DragStart MouseClick
| ...
onMouseDown : Attribute Msg
onMouseDown =
on "mousedown" (Json.Decode.map DragStart mouseClickDecoder)
Then in our update
function that is going to handle the DragStart
message we can check the value of button
and ignore right-clicks. There are some inconsistencies between browsers on how different buttons are represented as integers which you can read about on quirksmode. Fortunately they all align for the 'right-click' which is represented as 2
. So we can ignore that:
DragStart click ->
-- Ignore right-clicks which are given value '2' by browsers
if click.button /= 2 then
let
pos =
{ x = click.pageX, y = click.pageY }
in
({ model | = drag = Just { start = pos, current = pos } }, Cmd.none)
else
(model, Cmd.none)
And there we have it! We're free of the right-clicks and can happily jump into the browser dev-tools whenever we like.
If you have any thoughts or advice, please let me know! Always happy to learn.
Update: Checkout Ilias' comment below. It really cleans up the message handling in the update:
- DragStart click ->
- -- Ignore right-clicks which are given value '2' by browsers
- if click.button /= 2 then
- let
- pos =
- { x = click.pageX, y = click.pageY }
- in
- { model | drag = Just { start = pos, current = pos } } ! []
- else
- model ! []
+ DragStart pos ->
+ { model | drag = Just { start = pos, current = pos } } ! []
Top comments (2)
One possibility to keep ignored events out of your
update
and - incidentally - encourage some more reuse, is to move the decision into the decoder:The gist is that if an event-decoder fails, the event just happens but no message is sent to Elm.
Thanks Ilias! That is much cleaner. A better expression of the intention as well. I'll update our code :)