Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R Shiny key input binding

Tags:

r

shiny

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)
  })

  # ...

})
like image 449
reinholdsson Avatar asked Jul 26 '14 17:07

reinholdsson


5 Answers

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);
    });
  ') 
like image 68
jdharrison Avatar answered Oct 19 '22 06:10

jdharrison


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

like image 38
tyluRp Avatar answered Oct 19 '22 06:10

tyluRp


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

like image 26
Simon.S.A. Avatar answered Oct 19 '22 05:10

Simon.S.A.


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;
      }
    }
  });
like image 38
gringer Avatar answered Oct 19 '22 05:10

gringer


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

like image 27
gss Avatar answered Oct 19 '22 04:10

gss