'R Shiny key input binding
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)
})
# ...
})
Solution 1:[1]
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
Solution 2:[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.codeinstead ofe.whichas this gives us a more useful description of what keys are pressed. E.g."KeyA"instead of65. However, this means we can not distinguish between capitals and lower case. - We use
keydownandkeyupinstead ofkeypressaskeypressappears to timeout. So if you press-and-hold a key,keypresswill 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
Solution 3:[3]
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;
}
}
});
Solution 4:[4]
I'm writing this answer to highlight what @Davor Josipovic says in comments:
Shiny.onInputChangeis deprecated in favor ofShiny.setInputValue.Shiny.setInputValueallows 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, becauseinput$do not change) - it is not necessary to usecountervariable orMath.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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | tyluRp |
| Solution 2 | Simon.S.A. |
| Solution 3 | |
| Solution 4 | gss |
