Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using elm higher order functions for keyboard events

I am trying to create a higher order function to create functions to capture only a specific key code. The code is inspired on Evan's onEnter function from his todomvc implementation which captures only the enter function.

onKeyCode : Int -> Msg -> Attribute Msg
onKeyCode keycode msg =
    let
        captureKey code =
            if code == keycode then
                msg
            else
                NoOp
    in
        on "keydown" (Json.map captureKey keyCode)

onEnter = onKeyCode 13
onEsc =  onKeyCode 27

And now I want to add that to an input component in the viewer:

input
 [ class "edit"
 , id ("todo-" ++ toString item.uid)
 , value item.message
 , onInput (UpdateItem item.uid)
 , onBlur (SwitchEditTodo item.uid False)
 , onEnter (SwitchEditTodo item.uid False)
 , onEsc (UndoEditTodo item.uid)
 ]
[]

If I only have the onEnter the code will work as expected but if I add the onEsc, the onEnter code is never executed. Where am I doing the mistake? is that a problem with the higher order function context or "on" mapping with multiple values in separate functions?

like image 416
Víctor R. Escobar Avatar asked Sep 27 '16 12:09

Víctor R. Escobar


1 Answers

You are adding two onkeydown attributes to the input element, and only one of them can win. The second in the list overwrites the first. Were it to use addEventListener behind the scenes, this would not be the case, but for now we can solve it with a slightly different approach.

You could write a onKeysDown function that accepts a list of possible key codes and the message they should invoke like this:

onKeysDown : List (Int, Msg) -> Attribute Msg
onKeysDown keys =
  let
    captureKey code =
      List.filterMap (\(k, m) -> if k == code then Just m else Nothing) keys
        |> List.head
        |> Maybe.withDefault NoOp
  in
    on "keydown" (Json.map captureKey keyCode)

You could then write shorthand functions for handling specific keys like this:

enter msg = (13, msg)
esc msg = (27, msg)

And now you can use it in your view like this:

input
    [ ...
    , onKeysDown
      [ enter <| SwitchEditTodo item.uid False
      , esc <| UndoEditTodo item.uid
      ]
    ]

This works because it generates a single keydown event handler attribute. And while you substitute a predefined tuple for a higher order function, it still gives you just as readable code.

like image 186
Chad Gilbert Avatar answered Nov 05 '22 06:11

Chad Gilbert