Elm Calculator Part 8 - Support Keypad Input

pianomanfrazier

Ryan Frazier

Posted on May 2, 2020

Elm Calculator Part 8 - Support Keypad Input

This post is part of a series. To see all published posts in the series see the tag "elm calculator book".If you wish to support my work you can purchase the 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

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.

Ellie app installed packages

To install a package in ellie-app use the search bar.

Install package on Ellie app

Or you can install a package locally on the command line.

elm install Gizra/elm-keyboard-event
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
        }
Enter fullscreen mode Exit fullscreen mode

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
        }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

to

update Msg -> Model -> ( Model, Cmd Msg )
Enter fullscreen mode Exit fullscreen mode

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 )
            ...
Enter fullscreen mode Exit fullscreen mode

The init also needs to return a command message.

init : ( Model, Cmd Msg )
init =
    ( { stack = []
        , ...
        }
    , Cmd.none
    )
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 )
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
pianomanfrazier
Ryan Frazier

Posted on May 2, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related