9

I want to listen to both keypress and keydown events in Elm. But if I have the following, only the keydown events will be listened to:

textarea
    [ onWithOptions "keypress" (Options False True) <| Json.Decode.map KeyPress keyCode
    , onWithOptions "keydown" (Options False True) <| Json.Decode.map KeyDown keyCode
    ] []

If I change the Options to not preventDefault, then both events will be listened to. But I need to preventDefault in order to not let tab keys from changing focus.

Any way to do this in Elm?

at.
  • 50,922
  • 104
  • 292
  • 461

3 Answers3

9

In Elm 19, to access keys pressed, you can use Browser.Events which is a part of the Elm Browser library. If you want to capture what key is down, you could use something like this:

import Json.Decode as Decode

type Key
  = Character Char
  | Control String

subscriptions : Model -> Sub Msg
subscriptions model =
    Browser.Events.onKeyDown keyDecoder

keyDecoder : Decode.Decoder Msg
keyDecoder =
    Decode.map toKey (Decode.field "key" Decode.string)


toKey : String -> Msg
toKey string =
    case String.uncons string of
        Just ( char, "" ) ->
            PressedLetter char

        _ ->
            Control string

Adapted from here.

HParker
  • 1,567
  • 15
  • 19
1

Pre Elm 0.19, I recommend you to use elm-lang/keyboard. This package uses subscriptions and it's very easy to use. You can subscribe to keydown and keyup at the same time.

For your specific case: The default action for a keydown event is to trigger a keypress event. If you prevent that default behaviour, you will not get the keypress events.

You can read more about keydown events here

Maybe you just need to use keyup and keydown.

I hope it helps you.

TankorSmash
  • 12,186
  • 6
  • 68
  • 106
nidstang
  • 175
  • 4
  • problem with elm-lang/keyboard is that doesn't work for specific input elements, that I'm aware of... seems to apply to the whole webpage only. – at. Jun 07 '17 at 01:07
  • Yes, but in your update function you can handle how those events interact with your model. I've edited my answer with more information about your case. – nidstang Jun 08 '17 at 11:32
1

Inspired by the docs and HParker's answer, I wrote up a more complete solution using Elm 0.19 and subscriptions.

It adds a Subscription for Browser.Events.onKeyUp/Down, and handles them in a top-level Msg with a subMsg for easier processing. It includes Alt, Ctrl, Shift, and Meta (Windows Key/Option).

Remember that onKeyUp/Down and onKeyPressed handle characters differently (where up/down use the appropriate case, where pressed does not).

import Browser.Events
import Json.Decode as Decode


type KeyEventMsg
    = KeyEventControl
    | KeyEventAlt
    | KeyEventShift
    | KeyEventMeta
    | KeyEventLetter Char
    | KeyEventUnknown String


type Msg
    = KeyPressedMsg KeyEventMsg
    | KeyReleasedMsg KeyEventMsg


update : Msg -> ( Model, Cmd Msg )
update msg =
    case msg of
        KeyPressedMsg keyEventMsg ->
            case keyEventMsg of
                KeyEventShift ->
                    ( { model | shiftIsPressed = True }, Cmd.none )

                _ ->
                    ( model, Cmd.none )

        KeyReleasedMsg keyEventMsg ->
            case keyEventMsg of
                KeyEventShift ->
                    ( { model | shiftIsPressed = False }, Cmd.none )

                _ ->
                    ( model, Cmd.none )


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ Browser.Events.onKeyDown keyPressedDecoder
        , Browser.Events.onKeyUp keyReleasedDecoder
        ]


keyPressedDecoder : Decode.Decoder Msg
keyPressedDecoder =
    Decode.map (toKeyEventMsg >> KeyPressedMsg) (Decode.field "key" Decode.string)


keyReleasedDecoder : Decode.Decoder Msg
keyReleasedDecoder =
    Decode.map (toKeyEventMsg >> KeyReleasedMsg) (Decode.field "key" Decode.string)


toKeyEventMsg : String -> KeyEventMsg
toKeyEventMsg eventKeyString =
    case eventKeyString of
        "Control" ->
            KeyEventControl

        "Shift" ->
            KeyEventShift

        "Alt" ->
            KeyEventAlt

        "Meta" ->
            KeyEventMeta

        string_ ->
            case String.uncons string_ of
                Just ( char, "" ) ->
                    KeyEventLetter char

                _ ->
                    KeyEventUnknown eventKeyString
TankorSmash
  • 12,186
  • 6
  • 68
  • 106