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