SuperManEver
SuperManEver

Reputation: 2362

How to handle Enter key press in input field?

I built a simple app for learning purposes and want to be able to dispatch an action when the user presses Enter key in input field

view : Model -> Html Action
  view model = 
    let 
      items = List.map (\ item -> li [] [ text item ]) model.items
    in
      div [] [
       input [ onInput Change, value model.content ] [],
       button [ onClick Add ] [ text "Submit" ],
       ul [] items
      ]

Here is the view code. I hope it will be enough to explain my intent for you. What I'd like to have is ability to dispatch some action when user presses the Enter key while he is entering some text to input field.

Upvotes: 32

Views: 12775

Answers (6)

bowsersenior
bowsersenior

Reputation: 12574

There's a good, straightforward solution to handling onEnter in the Elm version of TodoMVC:

import Html exposing (Attribute)
import Html.Events exposing (keyCode, on)
import Json.Decode as Json

onEnter : Msg -> Attribute Msg
onEnter msg =
    let
        isEnter code =
            if code == 13 then
                Json.succeed msg
            else
                Json.fail "not ENTER"
    in
        on "keydown" (Json.andThen isEnter keyCode)

Upvotes: 22

dotcs
dotcs

Reputation: 2296

You can manually bind to the keydown event with the generic on handler. Elm does currently not support onKeyDown handlers out of the box - but they are planned in the future.

It looks like the spec is moving away from event.keyCode and towards event.key. Once this is supported in more browsers, we may add helpers here for onKeyUp, onKeyDown, onKeyPress, etc. (Source)

Until then you can simply write your own handler and use keycode 13 (enter) to perform your actions. Open the following ellie-app to see how it works. Just enter some text in the input box and press enter to see the current state reflected in the div below the input box.

import Html exposing (text, div, input, Attribute)
import Browser
import Html.Events exposing (on, keyCode, onInput)
import Json.Decode as Json


main =
  Browser.sandbox 
  { init = 
    { savedText = ""
    , currentText = ""
    }
  , view = view
  , update = update
  }


view model =
  div [] 
  [ input [onKeyDown KeyDown, onInput Input] []
  , div [] [ text ("Input: " ++ model.savedText) ]
  ]

onKeyDown : (Int -> msg) -> Attribute msg
onKeyDown tagger =
  on "keydown" (Json.map tagger keyCode)


type Msg 
  = NoOp
  | KeyDown Int
  | Input String


update msg model =
  case msg of

    NoOp ->
      model

    KeyDown key ->
      if key == 13 then
        { model | savedText = model.currentText }
      else
        model

    Input text ->
      { model | currentText = text }

Upvotes: 26

Simon H
Simon H

Reputation: 21005

I liked Alon's answer and iterated on it a bit to create an attribute that responds to <enter> and <esc>

onEscEnter : String -> (String -> msg) -> Attribute msg
onEscEnter originalValue tagger =
    let
        handleKey : Int -> Jdec.Decoder Int
        handleKey code =
            if L.member code [ 13, 27 ] then
                -- Enter (13) or ESC (27)
                Jdec.succeed code
            else
                Jdec.fail "something to ignore"

        combiner : Int -> String -> msg
        combiner keyCode tgtVal =
            if keyCode == 13 then
                tagger tgtVal
            else if keyCode == 27 then
                tagger originalValue
            else
                Debug.crash "onEscEnter"

        keyCodeDecoder : Jdec.Decoder Int
        keyCodeDecoder =
            Jdec.andThen handleKey keyCode
    in
        on "keydown" (Jdec.map2 combiner keyCodeDecoder targetValue)

Upvotes: 2

NateW
NateW

Reputation: 2121

If you're willing to use the community package Html.Events.Extra http://package.elm-lang.org/packages/elm-community/html-extra/latest/Html-Events-Extra#onEnter it is very easy.

(Assuming that you want to send the Add message when the enter key is pressed.)

import Html.Events.Extra exposing (onEnter)

view : Model -> Html Action
  view model = 
    let 
      items = List.map (\ item -> li [] [ text item ]) model.items
    in
      div [] [
       input [ onInput Change, onEnter Add, value model.content ] [],
       button [ onClick Add ] [ text "Submit" ],
       ul [] items
      ]

Upvotes: 5

AIon
AIon

Reputation: 13071

The above answers were very good - but storing each letter in the Model on every key press - is not always a good idea.

For example in my case i have a fileSystem-like strucutre - and i want to edit any name - no matter how nested it is - on doubbleclick. I can't have the hole fileSystem view reconstructed with each key press. It's laggy.

I found that it's best to receive the input value - only when the user presses Enter..

type Msg =
    | EditingStarted
    | EditingFinished String
    | CancelEdit

input [ whenEnterPressed_ReceiveInputValue EditingFinished, whenEscPressed_CancelOperation CancelEdit, onBlur CancelEdit ] []

update msg model =
    case msg of
        EditingFinished inputValue ->
            { model | name = inputValue }
        CancelEdit -> ...


whenEnterPressed_ReceiveInputValue : (String -> msg) -> H.Attribute msg
whenEnterPressed_ReceiveInputValue tagger =
  let
    isEnter code =
        if code == 13 then
            JD.succeed "Enter pressed"
        else
            JD.fail "is not enter - is this error shown anywhere?!"

    decode_Enter =
        JD.andThen isEnter E.keyCode
  in
    E.on "keydown" (JD.map2 (\key value -> tagger value) decode_Enter E.targetValue)


whenEscPressed_CancelOperation : msg -> H.Attribute msg
whenEscPressed_CancelOperation tagger =
  let
    isESC code =
        if code == 27 then
            JD.succeed "ESC pressed"
        else
            JD.fail "it's not ESC"

    decodeESC =
        JD.andThen isESC E.keyCode
  in
    E.on "keydown" (JD.map (\key -> tagger) decodeESC)

Note: If you are doing time-traveling debugging - you will not see each letter appearing as it was typed. But all text at once - because there was only one msg.. Depending on what you do - this can be a problem. If not, Enjoy :)

Upvotes: 5

Tosh
Tosh

Reputation: 36030

You could use something like this in your input element, this will fire the given message if enter key gets pressed down:

onEnterPressed : msg -> Attribute msg
onEnterPressed msg =
  let
    isEnter code =
      if code == 13 then Ok () else Err ""
    decodeEnterKeyCode = Json.customDecoder keyCode isEnter
  in on "keydown" <| Json.map (\_ -> msg) decodeEnterKeyCode

Upvotes: 5

Related Questions