miso

Core concepts

Events

By default all events are delegated through <body>. miso supports both the capture and bubble phases of browser events, and applications can handle either.

Using events

miso exposes defaultEvents for convenience — commonly used events that are listened for on <body> and routed through the View to the virtual DOM node that raised them. Other groups are exposed as conveniences too (keyboardEvents, mouseEvents, pointerEvents, touchEvents, …). All events required by all your components must be combined when running the application:

main = startApp (defaultEvents <> keyboardEvents <> touchEvents) app

touchEvents :: Events
touchEvents = M.fromList
  [ ("touchstart",  BUBBLE)
  , ("touchcancel", BUBBLE)
  , ("touchmove",   BUBBLE)
  , ("touchend",    BUBBLE)
  ]

Defining event handlers

Define your own handlers with the on combinator. By default this defines an event in the BUBBLE phase; see onCapture for the CAPTURE phase and onWithOptions for preventDefault / stopPropagation. Miso.Html.Event has many predefined events.

onChangeWith :: (MisoString -> DOMRef -> action) -> Attribute model action
onChangeWith = on "change" valueDecoder

The *With variant of an event (e.g. onChangeWith) provides the target DOMRef to the callback.

Decoding events

After an event is raised, information is extracted from it with a Decoder. Many common decoders are available in Miso.Event.Decoder.

data Decoder a = Decoder
  { decoder  :: Value -> Parser a   -- Miso.JSON parser
  , decodeAt :: DecodeTarget        -- path into the event object
  }

-- | A custom Decoder for the `value` property of an event target.
valueDecoder :: Decoder MisoString
valueDecoder = Decoder {..}
  where
    decodeAt = DecodeTarget ["target"]
    decoder  = withObject "target" $ \o -> o .: "value"

A decoder that reads several fields, used with on:

clickDecoder :: Decoder (Int, Int)
clickDecoder = Decoder
  { decodeAt = DecodeTarget []
  , decoder  = withObject "click" $ \o -> do
      ox <- o .: "offsetX"
      oy <- o .: "offsetY"
      pure (floor ox, floor oy)
  }

view = H.canvas_ [ on "click" clickDecoder (\(x, y) _ _ -> Clicked x y) ] []

Try it

Built-in handlers and a custom decoderlive

value:

keyCode: –

click me
data EventsModel = EventsModel
  { _typed :: MisoString, _lastKey :: Maybe Int, _clickAt :: Maybe (Int, Int) }
  deriving (Show, Eq)

data EventsAction
  = Typed MisoString
  | Pressed KeyCode
  | ClickedAt (Int, Int)

-- A custom decoder: read offsetX / offsetY from the raw event object.
offsetDecoder :: Decoder (Int, Int)
offsetDecoder = Decoder
  { decodeAt = DecodeTarget []
  , decoder  = withObject "click" $ \o ->
      (,) <$> (floor <$> (o .: "offsetX" :: Parser Double))
          <*> (floor <$> (o .: "offsetY" :: Parser Double))
  }

events :: Component ctx () EventsModel EventsAction
events = component (EventsModel "" Nothing Nothing) update view
  where
    update = \case
      Typed s               -> modify (\m -> m { _typed = s })
      Pressed (KeyCode k)   -> modify (\m -> m { _lastKey = Just k })
      ClickedAt xy          -> modify (\m -> m { _clickAt = Just xy })

    view _ () m =
      H.div_ []
        [ H.input_
            [ HP.placeholder_ "Type, then press keys…"
            , HE.onInput Typed                    -- "input"   (in defaultEvents)
            , HE.onKeyDown Pressed                -- "keydown" (needs keyboardEvents)
            ]
        , H.p_ [] [ "value: ", H.code_ [] [ text (_typed m) ] ]
        , H.p_ [] [ "keyCode: ", text (maybe "–" ms (_lastKey m)) ]
        , H.div_
            [ HP.class_ "target"
            , on "click" offsetDecoder (\xy _ _ -> ClickedAt xy)   -- custom decoder
            ]
            [ text (maybe "click me" (\(x, y) -> "clicked at " <> ms x <> "," <> ms y) (_clickAt m)) ]
        ]