5

I am having some trouble getting the functionality in my app that I'm looking for because of the way observeEvent works (which is normally very intuitive).

The basic functionality I'm looking for is that a user can input a couple numbers, click "submit", and then a modal pops up to take the user's name. After that, the app records the name and sums the numbers, and then clears the inputs. Then I'd like the user to be able repeat the process using the same name - but the app currently is structured so that the sums use an observeEvent that responds only when the name is different (i.e., using the same name twice in a row doesn't work, though I'd like it to). You can see in the app that my attempt at a solution is to reset the input for the inputSweetAlert (using shinyjs), but it can't access it, I assume because it's not actually on the UI side. I am using shinyWidgets sweetAlerts, which I'd like to continue doing.

Here's an example app:

library("shiny")
library("shinyWidgets")
library("shinyjs")

ui <- fluidPage(
  shinyjs::useShinyjs(),
  numericInput("num1", "Enter a number", value=NULL),
  numericInput("num2", "Enter another number", value=NULL),
  actionButton(inputId = "go", label = "submit"),
  verbatimTextOutput(outputId = "res1"),
  verbatimTextOutput(outputId = "res2")
)

server <- function(input, output, session) {

  observeEvent(input$go, {
    inputSweetAlert(session = session, inputId = "name", title = "What's your name?")
  })

  x <- reactiveValues(val=NULL)

  observeEvent(input$name, {
    x$val <- input$num1 + input$num2
    confirmSweetAlert(session = session, inputId = "confirmed", title = "Success!", text = "Your responses have been recorded. All is in order.", type = "success", btn_labels = c("Ok, let me continue")
    )
  })

  ## A possible approach to a solution...
  observeEvent(input$confirmed, {
    shinyjs::reset("num1") 
    shinyjs::reset("num2") 
    shinyjs::reset("name")
  })

  output$res1 <- renderPrint(paste("Name:", input$name))
  output$res2 <- renderPrint(paste("Sum:", x$val))
}

shinyApp(ui = ui, server = server)

Thanks for any help you can provide!

phalteman
  • 3,442
  • 1
  • 29
  • 46
  • Hi, which version of `shinyWidgets` do you use ? – Victorp Sep 29 '19 at 18:44
  • @Victorp, I'm on 0.4.9. – phalteman Sep 30 '19 at 19:09
  • Thanks, it works for me, weird, when you use `inputSweetAlert` the second time, the value is set to `NULL`. Nonetheless I've implemented @ismirsehregal suggestion in last dev version of shinyWidgegts – Victorp Sep 30 '19 at 19:32
  • Hmm...oddly enough it now works for me too, though it very clearly didn't have that behaviour before. Either way, thanks for including that behaviour more explicitly! – phalteman Sep 30 '19 at 19:41

2 Answers2

5

You can reset input$name via JS:

runjs('Shiny.setInputValue("name", null, {priority: "event"});')

Here is a working example:

library("shiny")
library("shinyWidgets")
library("shinyjs")

ui <- fluidPage(
  shinyjs::useShinyjs(),
  numericInput("num1", "Enter a number", value = NULL),
  numericInput("num2", "Enter another number", value = NULL),
  actionButton(inputId = "go", label = "submit"),
  verbatimTextOutput(outputId = "res1"),
  verbatimTextOutput(outputId = "res2")
)

server <- function(input, output, session) {
  
  observeEvent(input$go, {
    inputSweetAlert(session = session, inputId = "name", title = "What's your name?")
    runjs('Shiny.setInputValue("name", null, {priority: "event"});')
  })
  
  x <- reactiveValues(val = NULL)
  
  observeEvent(input$name, {
    x$val <- input$num1 + input$num2
    confirmSweetAlert(session = session, inputId = "confirmed", title = "Success!", text = "Your responses have been recorded. All is in order.", type = "success", btn_labels = c("Ok, let me continue"))
  })
  
  observeEvent(input$confirmed, {
    shinyjs::reset("num1") 
    shinyjs::reset("num2") 
    shinyjs::reset("mytext")
  })
  
  output$res1 <- renderPrint(paste("Name:", input$name))
  output$res2 <- renderPrint(paste("Sum:", x$val))
}

shinyApp(ui = ui, server = server)

For further information please see this article.

EDIT: In apps using modules, the call to runjs() can be adapted like this in order to namespace the id:

runjs(paste0("Shiny.setInputValue(\"", ns("name"), "\", null, {priority: \"event\"});"))
ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
  • That works (+1) but I don't understand. With this solution, why `input$name` is not `NULL` in `renderPrint(paste("Name:", input$name))`? – Stéphane Laurent Sep 16 '19 at 07:38
  • It is `NULL` right after clicking the submit button. – ismirsehregal Sep 16 '19 at 07:40
  • Yes, so why we get the non-null name in the `renderPrint` and not `NULL` ? – Stéphane Laurent Sep 16 '19 at 07:41
  • It is also `NULL` in the `renderPrint` after clicking the submit button (in the background when the modal is open) – ismirsehregal Sep 16 '19 at 07:43
  • Sorry to bother you mith my misunderstanding. And why does it switch to the non-null name when the modal is closed ? – Stéphane Laurent Sep 16 '19 at 07:44
  • because you entered a name? – ismirsehregal Sep 16 '19 at 07:45
  • Aahh I start to understand. The `inputSweetAlert` is waiting, so when you enter a name, this happens after the `setInputValue`. – Stéphane Laurent Sep 16 '19 at 07:47
  • yes, exactly - once you click the submit button, `input$name` is falling back to `NULL`. So everytime you enter a name, even though it's the same as before, it triggers the `observeEvent`. – ismirsehregal Sep 16 '19 at 07:50
  • What I don't understand now is why the shinyWidgets implementation does not work. See the [code here](https://github.com/dreamRs/shinyWidgets/blob/43b890a9d1137a02bb7160811e02cb513223a005/inst/www/sweetAlert/sweetalert-bindings.js#L58). The input is set to `null` and then it is set to the name. So I expected that this implementation was correct. – Stéphane Laurent Sep 16 '19 at 07:52
  • 4
    I guess they'd need to use `{priority: "event"}`. Also see [this](https://github.com/rstudio/shiny/issues/928). – ismirsehregal Sep 16 '19 at 07:56
  • 1
    Really nice solution, and thanks for the additional resources. I wish I'd known about that sooner! – phalteman Sep 16 '19 at 18:55
  • 1
    For posterity: When I modularized my app and had to split up the `js` command in order to namespace the `id`, I adapted your solution like this, which continues to work brilliantly: `runjs(paste0("Shiny.setInputValue(\"", ns("name"), "\", null, {priority: \"event\"});"))` – phalteman Sep 18 '19 at 19:50
  • Thanks for the addition! – ismirsehregal Sep 18 '19 at 20:11
  • Hello, I can update the package by adding `{priority: "event"}` if it's the solution. It will be less hacky – Victorp Sep 24 '19 at 17:56
  • For context: added a comment in the [GitHub issue](https://github.com/dreamRs/shinyWidgets/issues/222) raised by @StéphaneLaurent – ismirsehregal Sep 25 '19 at 06:46
  • @ismirsehregal, I edited your response to preserve the modular piece in case comments are deleted in the future. Hope that's ok with you. – phalteman Mar 23 '22 at 16:58
  • @phalteman improvements are always welcome! Cheers – ismirsehregal Mar 23 '22 at 17:02
2

Here is a workaround. The idea consists in changing the input id at each click on the button.

library("shiny")
library("shinyWidgets")
library("shinyjs")

ui <- fluidPage(
  shinyjs::useShinyjs(),
  numericInput("num1", "Enter a number", value=NULL),
  numericInput("num2", "Enter another number", value=NULL),
  actionButton(inputId = "go", label = "submit"),
  verbatimTextOutput(outputId = "res1"),
  verbatimTextOutput(outputId = "res2")
)

server <- function(input, output, session) {

  go <- reactive({
    input$go
  })

  observeEvent(input$go, {
    inputSweetAlert(session = session, inputId = sprintf("name-%d", go()), 
                    title = "What's your name?")
  })

  x <- reactiveValues(val=NULL)

  observeEvent(input[[sprintf("name-%d", go())]], {
    x$val <- input$num1 + input$num2
    confirmSweetAlert(session = session, inputId = "confirmed", title = "Success!", text = "Your responses have been recorded. All is in order.", type = "success", btn_labels = c("Ok, let me continue")
    )
  })

  ## A possible approach to a solution...
  observeEvent(input$confirmed, {
    shinyjs::reset("num1") 
    shinyjs::reset("num2") 
    shinyjs::reset("mytext")
  })

  output$res1 <- renderPrint(paste("Name:", input[[sprintf("name-%d", go())]]))
  output$res2 <- renderPrint(paste("Sum:", x$val))
}

shinyApp(ui = ui, server = server)
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • Also a feasible approach! – ismirsehregal Sep 16 '19 at 07:58
  • Thanks for this response. A great approach, but in my actual use case, I refer to that input in several other places through the app, so @ismirsehregal's solution is cleaner and will require fewer downstream changes. – phalteman Sep 16 '19 at 18:51