R Shiny key input binding
Asked Answered
T

5

24

In a Shiny application, is it possible to have a binding that listens to what key a user presses down?

I'm not too familiar with JavaScript, but I'm looking for something like:

window.onkeydown = function (e) {
    var code = e.keyCode ? e.keyCode : e.which;
    alert(code);
};

where the key input is then to be used in server.R, e.g.:

shinyServer(function(input, output) {

  output$text <- renderText({
    paste('You have pressed the following key:', input$key)
  })

  # ...

})
Tsingyuan answered 26/7, 2014 at 17:17 Comment(0)
M
34

You can add a listener for keypresses. The Shiny.onInputChange can be used to bind the key pressed to a shiny variable:

library(shiny)
runApp( list(ui = bootstrapPage(
  verbatimTextOutput("results"),
  tags$script('
    $(document).on("keypress", function (e) {
       Shiny.onInputChange("mydata", e.which);
    });
  ') 
)
, server = function(input, output, session) {

  output$results = renderPrint({
    input$mydata
  })
}
))

for keydown events you can substitute:

  tags$script('
    $(document).on("keydown", function (e) {
       Shiny.onInputChange("mydata", e.which);
    });
  ') 
Mendicity answered 26/7, 2014 at 17:44 Comment(8)
Happy to help. You will need to look around for a table of keyboard keycodes.Mendicity
This was very helpful. It seems though that it doesn't work with arrows, shift keys etc... and typing a letter with caps on or off yields a recent result. It seems it really observes what "character" is typed. Any way to make it work with arrows ?Telephone
for non character keys to work you need keydown/keyup instead of key press: see hereWendling
why would keydown occur only once if key is pressed multiple times?Procephalic
@Procephalic Reactive variables in Shiny will only trigger when a variable changes. If the same key is pressed multiple times in a row, the variable is being set but not changed, so functions using that value won't trigger.Booker
Hi, thanks for this great solution. Just a remark, when you need to use a shinydashboard page, the tag$script part need to be placed inside the dashboardBody part.Emelinaemeline
I found linked solution useful if e.g. the enter key should activate a search input typed into a textbox. If courser is not in text field, ienter will not trigger search yet github.com/daattali/advanced-shiny/blob/master/proxy-click/…Particularism
Two remarks here: use Shiny.setInputValue since Shiny.onInputChange is deprecated. Use { key: e.which, nonce: Math.random() } to send on every key press.Monahon
C
5

I've been working on an R package {keys} to solve this problem. It's basically a wrapper around the Mousetrap javascript library. With this, observing keys is an easy task:

library(shiny)
library(keys)

hotkeys <- c(
  "1", 
  "command+shift+k", 
  "up up down down left right left right b a enter"
)

ui <- fluidPage(
  useKeys(),
  keysInput("keys", hotkeys)
)

server <- function(input, output, session) {
  observeEvent(input$keys, {
    print(input$keys)
  })
}

shinyApp(ui, server)

More information here: https://github.com/r4fun/keys

Coughlin answered 8/10, 2020 at 14:6 Comment(0)
C
3

I'm writing this answer to highlight what @Davor Josipovic says in comments:

  1. Shiny.onInputChange is deprecated in favor of Shiny.setInputValue.
  2. Shiny.setInputValue allows to more elegant solution for a problem with the same key event pressed twice (or more) in a row (by default in this situation event won't be trigger, because input$ do not change) - it is not necessary to use counter variable or Math.random() method, just {priority: "event"}:
library(shiny)

tags$script(HTML('
document.addEventListener("keydown", function(e) {
  Shiny.setInputValue("key_pressed", e.key, {priority: "event"});
    });
'))

Communicating with Shiny via JavaScript

Above I have used classic approach with JavaScript instead of jQuery and also I have used e.key instead of e.which or e.keyCode (both deprecated) Difference

Canned answered 25/2, 2022 at 9:4 Comment(0)
V
2

Building on @jdharrison and @gringer, I wanted a listener that would track not just whether a key has been pressed, but which keys are presently pressed/down.

This gives me the following:

library(shiny)

ui = bootstrapPage(
  verbatimTextOutput("results"),
  ## keydown
  tags$script('
    downKeyCount = 0;
    $(document).on("keydown", function (e) {
       Shiny.onInputChange("downKey", downKeyCount++);
       Shiny.onInputChange("downKeyId", e.code);
    });'
  ),
  ## keyup
  tags$script('
    upKeyCount = 0;
    $(document).on("keyup", function (e) {
       Shiny.onInputChange("upKey", upKeyCount++);
       Shiny.onInputChange("upKeyId", e.code);
    });'
  )
)

server = function(input, output, session){
  keyRecords = reactiveValues()

  output$results = renderPrint({ 
    keys = reactiveValuesToList(keyRecords);
    names(keys[unlist(keys)]);
  })
  observeEvent(input$downKey, { keyRecords[[input$downKeyId]] = TRUE });
  observeEvent(input$upKey, { keyRecords[[input$upKeyId]] = FALSE });
}

shinyApp(ui = ui, server = server)

Key details/changes from above:

  • We use e.code instead of e.which as this gives us a more useful description of what keys are pressed. E.g. "KeyA" instead of 65. However, this means we can not distinguish between capitals and lower case.
  • We use keydown and keyup instead of keypress as keypress appears to timeout. So if you press-and-hold a key, keypress will be active (return TRUE) for 1-2 seconds and then be inactive (return FALSE).
  • We initially used the simple listener by @jdharrison. However we use the key-pressed-count method by @gringer in our code above as otherwise pressing or releasing the same key twice in a row does not respond. But if you want to force users to alternate key presses, then we recommend the simple listener.

Shiny v1.4, R v3.6.2

Verner answered 19/4, 2020 at 0:7 Comment(0)
B
1

If you want this to work for multiple presses, you can create an additional press counter that updates on a key press (following the increment convention used in Shiny), bind that value to a different variable, and watch the counter rather than the key code. Here's some example UI code:

  tags$script('
    pressedKeyCount = 0;
    $(document).on("keydown", function (e) {
       Shiny.onInputChange("pressedKey", pressedKeyCount++);
       Shiny.onInputChange("pressedKeyId", e.which);
    });'
  )

And associated server code:

  observeEvent(input$pressedKey, {
    if(input$pressedKeyId >= 49 && input$pressedKeyId <= 57){ # numbers
      values$numClick <- (input$pressedKeyId - 48);
      flipNumber();
    }
    if(input$pressedKeyId >= 37 && input$pressedKeyId <= 40){ # arrow keys
      arrowCode <- input$pressedKeyId - 37;
      xInc <- ((arrowCode+1) %% 2) * (arrowCode - 1);
      yInc <- ((arrowCode) %% 2) * (arrowCode - 2) * -1;
      if(!any(values$click == c(-1,-1))){
        values$click <- (((values$click - 1) + c(xInc, yInc) + 9) %% 9) + 1;
      }
    }
  });
Booker answered 31/3, 2018 at 1:44 Comment(2)
No need to complicate: you can simply send { key: e.which, nonce: Math.random() }. Since nonce is random and different on each key-press, the message will be sent every time.Monahon
I don't agree that a random number generator is simpler (or less computationally intensive) than an incrementing value. It's also not guaranteed to be non-repeating (although the likelihood of that is very small with a floating point value).Booker

© 2022 - 2024 — McMap. All rights reserved.