Imagine you need to do something when the enter key is pressed but don’t care
when any other keys are pressed. What if you don’t always want to react to the
keyup event? How would you do it in an Elm app?
I can think of two ways to tackle the problem:
- Write an
onKeyuphandler and send every event’s keycode to the
updatefunction for evaluation.
- Write an
onEnterhandler and check the keycode in the handler function first. Only send on to the
updatefunction if the enter key was pressed.
onKeyup handler seems like it would be easier to write but having to
check the keycode in our
update function each time feels clunky.
onEnter handler sounds like a tidier solution. But how do you build that
conditional logic into the handler function?
How DOM event handler functions work in Elm
In Elm, the
Html.Events package provides the things you need for handling
events, including some built in handlers like
Html.Events.onClick. It also
Html.Events.on, the function used to build
and which any developer can use to build their own custom DOM event handlers.
Html.Events.on works is that you give it the string name of the event
to react to and a JSON Decoder to decode the event object with. Whatever
values you successfully decode are then sent on to your
A great example of how this works is
Html.Events.onClick, which doesn’t
extract any values from the event object at all. Instead it uses
Json.Decode.succeed to ignore the JSON all together and successfully return
onClick : msg -> Attribute msg onClick msg = on "click" (Json.succeed msg)
Json.Decode has been aliased to
Json in the file where
The source docs don’t mention what happens when the decoder fails. But you can
try it out in this Ellie App example and see for yourself. Spoiler alert:
nothing happens. It is allowed to fail silently, and nothing is sent on to the
update function. This can make them tricky to debug, but it gives us a way to
conditionally react to events.
Sometimes you have to
Json.Decode.fail to succeed
Elm developers can leverage this behavior to conditionally handle events and keep all the conditional logic inside of their handler function.
To write an
onEnter handler this way you would need to do the following:
Extract the keycode from the event object with a decoder
If the keycode is
13(the enter key), use
Json.succeedto return a successful message, just like in
onClick. Otherwise, return
For the first step, the
Html.Events package includes a decoder for extracting
the keycode from an event object, called
Html.Events.keyCode. We can use
For the second step, there is a handy function in the
Json package called
Json.Decode.andThen that’s especially good at doing just this thing: it lets
us choose a decoder to return, based on the value passed in. Unfortunately,
diving into how
andThen works is beyond the scope of this short article but is
well worth exploring. In the mechanics of
Maybe my colleague Joël explores
Maybe.andThen which works in a similar way to
explore decoders in general, Joël has a few other great articles.
Ready to put all this together and make something?
Using everything we just learned we could write an implementation of
onEnter : msg -> Html.Attribute msg onEnter msg = let isEnterKey keyCode = if keyCode == 13 then Json.succeed msg else Json.fail "silent failure :)" in on "keyup" <| Json.andThen isEnterKey Html.Events.keyCode
You can play around with
onEnter in this Ellie App.
Html.Events package authors decided to ignore when decoders fail in
event handlers. We were able to lean on that to write a decoder that decides
whether to fail or not based on some characteristic of the event. In our case,
we were interested in the event’s keycode. This allowed us to keep the
conditional logic out of our
update function and within our custom handler