Reputation: 2362
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
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
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
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
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
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
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