Hey,
One feature I receive from clients when building a Shiny App is the ability to "validate" through pressing the Enter key when using a textInput().
Example: You use textInput() as a Search Form, with a button to validate the search just below. I've been asked several time to implement a validation of search through pressing the Enter button here, which I always do locally via jQuery but I feel like there should be a more systemic way to do it.
I think it would be nice to have a native keypress event here, the same way we have a click event on plotOutput()
My suggestion:
pkgload::load_all()
textInput <- function(inputId, label, value = "", width = NULL,
placeholder = NULL) {
value <- restoreInput(id = inputId, default = value)
div(class = "form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
label %AND% tags$label(label, `for` = inputId),
tags$input(
id = inputId,
type="text",
class="form-control",
value=value,
placeholder = placeholder,
onKeyPress = sprintf("Shiny.setInputValue('%s_keypress', event.key)", inputId)
)
)
}
library(shiny)
ui <- function(request){
tagList(
textInput("plop","plop"),
br(),
textOutput("text")
)
}
server <- function(input, output, session){
observeEvent( input$plop_keypress, {
print(input$plop_keypress)
if (input$plop_keypress == "Enter"){
output$text <- renderText({
"You've validated"
})
}
})
}
shinyApp(ui, server)
As you can see, the key is printed to the console, and we can create conditional events on pressing the 'Enter' button.
Tested on :
Things to look at :
plotOutput(), maybe this can be the same here. My personal preference is inputId_keypress though, but I also could understand the need for consistency :) If ever you think this is a good idea, I'll be glad to do a PR.
Colin
This would also be nice for other inputs like numericInput, passwordInput or textAreainput.
Seconded - easy form validation in Shiny via R is the biggest gap that I see in the ecosystem.
Here's a suggestion for numericInput() and passwordInput()
numericInputpkgload::load_all()
numericInput <- function(
inputId,
label,
value,
min = NA,
max = NA,
step = NA,
width = NULL
) {
value <- restoreInput(id = inputId, default = value)
# build input tag
inputTag <- tags$input(id = inputId, type = "number", class="form-control",
value = formatNoSci(value))
if (!is.na(min))
inputTag$attribs$min = min
if (!is.na(max))
inputTag$attribs$max = max
if (!is.na(step))
inputTag$attribs$step = step
div(
class = "form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
label %AND% tags$label(label, `for` = inputId),
inputTag,
onKeyPress = sprintf("Shiny.setInputValue('%s_keypress', event.key)", inputId)
)
}
library(shiny)
ui <- function(request){
tagList(
numericInput("plop","plop", min = 0, max = 10, value = 5),
br(),
textOutput("text")
)
}
server <- function(input, output, session){
observeEvent( input$plop_keypress, {
print(input$plop_keypress)
if (input$plop_keypress == "Enter"){
output$text <- renderText({
input$plop
})
}
})
}
shinyApp(ui, server)
passwordInput()pkgload::load_all()
passwordInput <- function(
inputId,
label,
value = "",
width = NULL,
placeholder = NULL
) {
div(class = "form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
label %AND% tags$label(label, `for` = inputId),
tags$input(
id = inputId,
type="password",
class="form-control",
value=value,
placeholder = placeholder,
onKeyPress = sprintf("Shiny.setInputValue('%s_keypress', event.key)", inputId)
)
)
}
library(shiny)
ui <- function(request){
tagList(
passwordInput("plop","plop"),
br(),
textOutput("text")
)
}
server <- function(input, output, session){
observeEvent( input$plop_keypress, {
print(input$plop_keypress)
if (input$plop_keypress == "Enter"){
if (input$plop == "password")
output$text <- renderText({
"Unlocked!"
})
}
})
}
shinyApp(ui, server)
I'm not sure of a context where it would be useful in a textAreaInput() — the main use case for this being the key press of 'Enter', and textAreaInput() uses Enter to write a new line. But if ever:
pkgload::load_all()
textAreaInput <- function(inputId, label, value = "", width = NULL, height = NULL,
cols = NULL, rows = NULL, placeholder = NULL, resize = NULL) {
value <- restoreInput(id = inputId, default = value)
if (!is.null(resize)) {
resize <- match.arg(resize, c("both", "none", "vertical", "horizontal"))
}
style <- paste(
if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
if (!is.null(height)) paste0("height: ", validateCssUnit(height), ";"),
if (!is.null(resize)) paste0("resize: ", resize, ";")
)
# Workaround for tag attribute=character(0) bug:
# https://github.com/rstudio/htmltools/issues/65
if (length(style) == 0) style <- NULL
div(class = "form-group shiny-input-container",
label %AND% tags$label(label, `for` = inputId),
tags$textarea(
id = inputId,
class = "form-control",
placeholder = placeholder,
style = style,
rows = rows,
cols = cols,
value,
onKeyPress = sprintf("Shiny.setInputValue('%s_keypress', event.key)", inputId)
)
)
}
library(shiny)
ui <- function(request){
tagList(
textAreaInput("plop","plop"),
br(),
textOutput("text")
)
}
server <- function(input, output, session){
observeEvent( input$plop_keypress, {
print(input$plop_keypress)
if (input$plop_keypress == "Enter"){
if (input$plop == "password")
output$text <- renderText({
"Unlocked!"
})
}
})
}
shinyApp(ui, server)
Just noticed a pblm: if you press "Enter" several times, you'll only have a Shiny event once (as the value doesn't change).
Can be solved with adding the time with sprintf("Shiny.setInputValue('%s_keypress', {value : event.key, time : new Date()})", inputId):
pkgload::load_all()
textInput <- function(inputId, label, value = "", width = NULL,
placeholder = NULL) {
value <- restoreInput(id = inputId, default = value)
div(class = "form-group shiny-input-container",
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
label %AND% tags$label(label, `for` = inputId),
tags$input(
id = inputId,
type="text",
class="form-control",
value=value,
placeholder = placeholder,
onKeyPress = sprintf("Shiny.setInputValue('%s_keypress', {value : event.key, time : new Date()})", inputId)
)
)
}
library(shiny)
ui <- function(request){
tagList(
textInput("plop","plop"),
br(),
textOutput("text")
)
}
server <- function(input, output, session){
observeEvent( input$plop_keypress, {
print(input$plop_keypress)
if (input$plop_keypress$value == "Enter"){
output$text <- renderText({
"You've validated"
})
}
})
}
shinyApp(ui, server)
I agree for the textAreaInput, it would probably make more sense to trigger the observeEvent with CTRL+Enter for example or another combination.
@ColinFay you can also do Shiny.setInputValue('keypress', {value : event.key}, {priority: 'event'})
@cpsievert That's indeed better :)
Otherwise, there's function searchInput that does exactly that in shinyWidgets package.