4

I've built a Shiny app that has a popup message if a user puts in a non-numeric value into a numericInput. The code below does just that (using shinyBS), but it reacts too quickly. If the user starts entering text, but needs to think a little about the number to put in, the message pops up. I'd like the observeEvent to trigger when the input loses focus, so the user has time to enter the full value, and only when they move on to the next input does the observeEvent handle the error. Any suggestions? I'm not wedded to using shinyBS if there's a better solution out there, I built this app like 4 years ago and the Shiny universe has grown considerably since then.

library(shinyBS)
library(shiny)


ui<-shinyUI(fluidPage(
  fluidRow(
      numericInput("value","My Value",value=0),
      bsModal("number_Message", "", trigger="", size = "small","This field only accepts numeric values.")
      )
    ))

server<-shinyServer(function(input, output, session){
  output$value<-renderText({input$value})
  observeEvent(input$value,{
  #  browser()
    if(is.na(as.numeric((input$value)))==T){
      toggleModal(session, "number_Message",toggle="toggle")
      updateNumericInput(session,"value","My Value",value=0)
    }
  })
})

shinyApp(ui = ui, server = server)

1 Answers1

6

Your best bet is probably using a small custom js script to register an event handler on the loses focus. The callback could then either send a custom message to Shiny or trigger a dialog box directly on the client side.

Assuming you still want the server to handle it, create a separate file in your www directory

// www/customMessage.js

$(document).ready(function() {

  // initialize a counter
  var n = 0;

  // create a handler
  $("#value").on("blur", function(){

    // increment the counter each time input loses focus
    n++;

    // send message to Shiny
    Shiny.onInputChange("count", n);
  });

});

Then use includeScript('www/customMessage.js') in your UI definition and change the trigger from input$value to input$count in the server definition.

I'm glad that you're not wedded to shinyBS, because it messes with the jquery functionality to add the event handler. There are other options out there, but Shiny also has a native modal. All together, your app code would look something along the lines of

library(shiny)

ui <- shinyUI(fluidPage(
  includeScript("www/customMessage.js"),

  fluidRow(
    numericInput("value","My Value",value=0)
  )

))

server<-shinyServer(function(input, output, session){
  output$value<-renderText({input$value})

  observeEvent(input$count,{

    if(is.na(as.numeric((input$value)))==T){

      showModal(modalDialog(
        title = "Invalid Input",
        "This field only accepts numeric values."
      ))

      updateNumericInput(session,"value","My Value",value=0)
    }
  })
})

shinyApp(ui = ui, server = server)
Marcus
  • 3,478
  • 1
  • 7
  • 16
  • This works for me with your example, but I'm trying to figure out why my actual script isn't registering the input$count in the observeEvent. I've verified it's reading in the .js file, but I can't get the observeEvent to be triggered by the count variable from .js – robin.datadrivers Jun 05 '20 at 19:00
  • If you're using shinyBS, it causes problems with Javascript/JQuery. I ran into this problem when putting together my response, which is why I took it out in my example. There might be a way to solve this, but I didn't have time to dive into it. To see if you are experiencing the same issue, load up your app and access the developer tools (in Chrome this is right click on the page and click 'inspect', similar for the RStudio view as well). Check the js console and there's likely a error message. – Marcus Jun 12 '20 at 16:58
  • I've cut out shinyBS entirely from the app. I can't get the Shiny app to respond to the JS - I basically cut and paste your text into a script file, works fine in the test app, but porting to mine it doesn't work. Shiny.onInputChange("flag", n) doesn't trigger the observeEvent. I disabled any package that uses JS. Inspecting the page doesn't throw up any errors either (that I can tell, I'm not an expert developer). Any other advice for what to look out for? I'd share the whole app but it's pretty complex (and proprietary). – robin.datadrivers Jun 16 '20 at 02:10
  • Usually my go to next step would be a) make sure `n` is incrementing on the client/js side then b) make sure the change is registering on the server/Shiny side. For a) after `Shiny.onInputChange("flag", n)` print n to the console with `console.log(n)`. Every time you blur the input, you should see `n` incrementally printed to the js console. For b) I typically just add an observer to solely print the flag when it's incremented. So in your server function add `observe(print(input$flag))`. When the input loses focus, n should also be printed incrementally to your R console. – Marcus Jun 17 '20 at 04:41