2

How do I make it so that Shiny waits a little bit for the user to input their zip code (without using a button). The problem I'm facing is that it will quickly jump to the error if the user isn't fast enough at inputting their zip code. Edit:

  • I have other user inputs in this app, but I simplified it in this question. I want all of the other inputs to be reacted to right away since they are radioButtons. I think pressing 'return' would still do the same thing as a button, which isn't what I'm looking for. I was thinking about using ' Sys.sleep()' only for the zipcode input and not for the others. Would that be possible?
library(shiny)

shinyApp(ui <- fluidPage(sidebarPanel(
  "",
  textInput("zipcode", label = "Enter your zipcode.", value = 98125)
  
)) ,


server <- function(input, output, session) {
  observeEvent(input$zipcode, {
    #limits zipcode input to 5 numbers only
    if (nchar(input$zipcode) != 5)
    {
      updateTextInput(session, 'zipcode', value = 98125)
      showModal(
        modalDialog(
          title = "Error!",
          "Only 5-character entries are permitted.",
          easyClose = TRUE
        )
      )
    }
    if (is.na(as.numeric(input$zipcode)))
    {
      showModal(
        modalDialog(
          title = "Error!",
          "Only numeric values are allowed. Please try again.",
          easyClose = TRUE
        )
      )
    }
  })
})
youtube
  • 265
  • 1
  • 7
  • One easy way would be to add a button they would click when they were done entering their zip code. Would that work? – DaveArmstrong Sep 20 '20 at 20:06
  • I didn't want to add a button-- is there a way around that? – youtube Sep 20 '20 at 20:10
  • of course there is such – polkas Sep 20 '20 at 20:19
  • I will be great if you reformat this code, in RStudio Ctrl+Shift+A. Thanks – polkas Sep 20 '20 at 20:26
  • 1
    Is this going to be a pleasant UI experience? Another approach is to put `req(nchar(input$zipcode) == 5)` in all of the reactives that depend on the zip code. You can also use `validate(need(nchar(input$zipcode) == 5, message = "Only 5-character zip entries are permitted."))` to give an error at the place of the output that needed the zip. – Michael Dewar Sep 21 '20 at 03:23
  • @MichaelDewar, I agree that your solution also works well, but I'm wondering how it would work for zip codes that have leading zeros like 00501. The nchar for this zip would be 3 characters. What would be the best way to account for this? – youtube Sep 24 '20 at 06:10
  • 1
    @youtube The `input$zipcode` will be a character string, so leading zeros will still have `nchar(input$zipcode) == 5`. You probably need to use the condition `nchar(input$zipcode) == 5 & !is.na(as.numeric(input$zipcode))` inside the `req` or `validate(need())`. If `input$zipcode` is used in many places, you could use `clean_zip <- reactive(validate(need(nchar(input$zipcode) == 5 & !is.na(as.numeric(input$zipcode))), message = "Only 5-digit zip allowed."); input$zipcode)` and then anywhere you use it, you just need to put a `req(clean_zip())` at the top. – Michael Dewar Sep 24 '20 at 12:46
  • 1
    Whatever method you use, I suggest that you simplify things and use `&` to join both conditions into one test. A single error message about 5-digit zips is enough. – Michael Dewar Sep 24 '20 at 12:54

2 Answers2

2

You could use debounce:

This lets you ignore a very "chatty" reactive expression until it becomes idle, which is useful when the intermediate values don't matter as much as the final value

You don't need Sys.sleep, it will wait millis milliseconds before triggering the reactives :

library(shiny)

shinyApp(
  ui <- fluidPage( 
    sidebarPanel("", textInput("zipcode", label="Enter your zipcode.", value = 98125)
                 
    )  ) , 
  
  
  server <- function(input, output, session) {
    zipcode <- reactive(input$zipcode)
    zipcode_d <- debounce(zipcode, millis = 2000)
    observeEvent(zipcode_d(),{     #limits zipcode input to 5 numbers only
      if(nchar(zipcode_d())!=5)
      {
        updateTextInput(session,'zipcode',value=98125)
        showModal(modalDialog(
          title = "Error!",
          "Only 5-character entries are permitted.",
          easyClose = TRUE
        ))
      }
      if(is.na(as.numeric(zipcode_d())))
      {
        showModal(modalDialog(
          title = "Error!",
          "Only numeric values are allowed. Please try again.",
          easyClose = TRUE
        ))
      }
    }
    )
  })
Waldi
  • 39,242
  • 6
  • 30
  • 78
1

JS code added, input$keyPressed which receives a random number when the "Return" key is pressed anywhere.

library(shiny)

js <- '
$(document).on("keyup", function(e) {
  if(e.keyCode == 13){
    Shiny.onInputChange("keyPressed", Math.random());
  }
});
'

shinyApp(ui <- fluidPage(tags$script(js),
                         sidebarPanel(
                           "",
                           textInput("zipcode", label = "Enter your zipcode.", value = 98125),
                           textOutput("zip")
                         )) ,
         
         server <- function(input, output, session) {
           observeEvent(input[["keyPressed"]], {
             #limits zipcode input to 5 numbers only
             if (nchar(input$zipcode) != 5)
             {
               updateTextInput(session, 'zipcode', value = 98125)
               showModal(
                 modalDialog(
                   title = "Error!",
                   "Only 5-character entries are permitted.",
                   easyClose = TRUE
                 )
               )
             }
             if (is.na(as.numeric(input$zipcode)))
             {
               showModal(
                 modalDialog(
                   title = "Error!",
                   "Only numeric values are allowed. Please try again.",
                   easyClose = TRUE
                 )
               )
             }
             output$zip <- renderText(input$zipcode)
           })
         })

linked to: Shiny Responds to Enter

polkas
  • 3,797
  • 1
  • 12
  • 25
  • Thanks for your idea! I have other user inputs in this app, but I simplified it in this question. I want all of the other inputs to be reacted to right away since they are radioButtons. I think pressing 'return' would still do the same thing as a button, which isn't what I'm looking for. I was thinking about using ' Sys.sleep()' only for the zipcode input and not for the others. Would that be possible? – youtube Sep 20 '20 at 20:29
  • So @Waldi solution might be better then. However be careful with putting such pressure on users. Giving them information how much time they still have to provide the input might be a nice idea. – polkas Sep 20 '20 at 20:33