This post is part of a series. If you wish to support my work you can purchase the PDF book on gumroad.
This project uses Elm version 0.19
- Part 1 - Introduction
- Part 2 - Project Setup
- Part 3 - Add CSS
- Part 4 - Basic Operations
- Part 5 - Adding Decimal Support
- Part 6 - Supporting Negative Numbers
- Part 7 - Add Dirty State
- Part 8 - Support Keypad Input (this post)
- Part 9 - Combination Key Input
- Part 10 - Testing
- Part 11 - Netlify Deployment
- browse: https://gitlab.com/pianomanfrazier/elm-calculator/-/tree/v0.8
- diff: https://gitlab.com/pianomanfrazier/elm-calculator/-/compare/v0.7...v0.8
- ellie: https://ellie-app.com/72nXvQqPq37a1
It would be nice if users could input numbers into our calculator using their number pad on their keyboard.
We need to tell our program what to do when a user presses these keys. So we need to add some more messages to our application.
We will be using the package Gizra/elm-keyboard-event.
First we need to install the package so we can import it into our code. The ellie-app version of this chapter has the package installed. Open the side menu and look at the installed packages. You should see I have added 3 new packages as dependencies.
To install a package in ellie-app use the search bar.
Or you can install a package locally on the command line.
elm install Gizra/elm-keyboard-event
We will also need to import SwiftsNamesake/proper-keyboard which is a dependency of elm-keyboard-event. We need this because we will be using these types directly in our application.
elm install SwiftsNamesake/proper-keyboard
And lastly we need elm/json
to decode the JSON message coming from the browser for our events. Don't worry. This is not as scary as it sounds.
elm install elm/json
Refactor to use Elm subscriptions
Since these keyboard events are going to be coming from the browser, our Elm application needs to subscribe to these events. In order to do subscriptions we need to refactor our application.
Right now we have been using Browser.sandbox
in our main function.
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
Change to Browser.element
We need to change it to Browser.element
so that we can add subscriptions.
main : Program () Model Msg
main =
Browser.element
{ view = view
, init = \_ -> init
, update = update
, subscriptions = subscriptions
}
This is going to impact our whole application. We'll let the compiler guide us through this refactor. The biggest change will be to our update
function.
It will need to change from
update : Msg -> Model -> Model
to
update Msg -> Model -> ( Model, Cmd Msg )
We need to return the model and a command message. We won't be using command messages in this application so don't worry about it. We just need to fix the code so it will compile again.
Most of the time we can just return the tuple ( model, Cmd.none )
.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SetDecimal ->
if String.contains "." model.currentNum then
( model, Cmd.none )
...
The init
also needs to return a command message.
init : ( Model, Cmd Msg )
init =
( { stack = []
, ...
}
, Cmd.none
)
Add subscriptions
Now we can work on adding the subscriptions. First we need to import some stuff.
import Browser.Events exposing (onKeyDown)
import Json.Decode as D
import Keyboard.Event as KE exposing (KeyboardEvent, decodeKeyboardEvent)
import Keyboard.Key as KK
We are now going to subscribe to the onKeyDown
event in the browser. After our application gets the event we then need to decode the event into something Elm can deal with. Since events come in the from the browser as a JSON message we need to decode the JSON into an Elm type.
Luckily for us, we don't need to worry about writing a decoder for these events. The Gizra/elm-keyboard
package provides this for us.
subscriptions : Model -> Sub Msg
subscriptions model =
onKeyDown (D.map HandleKeyboardEvent decodeKeyboardEvent)
This subscriptions
function is going to return a subscription message. In this case a HandleKeyboardEvent
type.
We need that in our message type.
type Msg
= ...
| HandleKeyboardEvent KeyboardEvent
Handle the key events
Now we can put this message and handle it in our update function.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
...
HandleKeyboardEvent event ->
case event.keyCode of
KK.Add ->
update (InputOperator Add) model
KK.Subtract ->
update (InputOperator Sub) model
KK.NumpadZero ->
update (InputNumber 0) model
...
-- ignore anything else
_ -> ( model, Cmd.none )
I used a small trick here. When we get a key matching one of our cases, just call the appropriate update function again.
What I hope you take away from this chapter is how nice it is to refactor things in Elm. We made some sweeping changes to our application and the compiler was able to help us out.
Now that we have keyboard input, it would be nice if the user had some more options for deleting the stack frames. The next chapter will cover using key combinations such as ctrl-shift-delete to clear out the frames.