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)
})
# ...
})
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);
});
')
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
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:
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.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).Shiny v1.4, R v3.6.2
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;
}
}
});
I'm writing this answer to highlight what @Davor Josipovic says in comments:
Shiny.onInputChange is deprecated in favor of Shiny.setInputValue.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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With