Event handling in SVG

I have a board game made in SVG. Each pieces is an SVG element.
I would like to do actions on the pieces using mouse and keyboard.
For now, I have:

data Action = Initialize
            | Select (Maybe Piece)
            | Rotate Piece
            | Move Piece Dir
            | ...

data Dir = N | E | S | W 

Piece = ...

-- Get the tile with position, events and highlight
getTile :: forall w. Piece -> HTML w Action 
getTile piece = 
  g [
        onClick \_ -> Select $ Just piece,       -- left click to select the piece
        onContextMenu \_ -> Rotate piece,        -- right click to rotate the piece
        onKeyDown $ onKeyDown piece,             -- arrow keys to move the piece, "r" to rotate
        tabIndex 0                               -- allows to tab through elements and receive keyboard inputs
          SE.svg [] $ 

onKeyDown :: Piece -> KeyboardEvent -> Action
onKeyDown p ke = case key ke of
   "ArrowDown" -> Move p S
   "ArrowUp"      -> Move p N
   "ArrowRight"  -> Move p E
   "ArrowLeft"     -> Move p W
   "r"              -> Rotate p

handleAction :: forall output m. MonadAff m => Action -> H.HalogenM UI Action () output m Unit
handleAction a = case a of
  Initialize -> do
  Select se     ->   H.modify_ $ updateUI se select
  Rotate se    ->   H.modify_ $ updateUI se rotate
  Move   se d ->   H.modify_ $ updateUI se (move d)

The problem I have is that events “ContextMenu” and “KeyDown” are bubbling: I would like to prevent that.
For instance right clicking will open the context menu.
I understood that I should use stopPropagation in my event handler.
However this is not ideal because I will need to pass the Event in my Action (for some of them anyway):

data Action = 
   Select Piece
   KeyDown KeyboardEvent

I would prefer to keep Action as everything you can do in the game.
Any suggestions?

I think a common pattern is to have

data Action
    = StopPropagation Action Event
    | ...

where you pass the Action you are actually interested in as an additional parameter and then just recursively all your handleAction:

handleAction (StopPropagation cont ev) = do
    stopPropagation ev
    handleAction cont

and you should be able to use this as onContextMenu (StopPropagation (Rotate piece))

1 Like

It look nice, thanks!