rshinyshinyalert

How to skip elements in chaining modal approach (shinyalert library)?


My problem with breaking a loop insidse shinyalert has been solved in this post.

I decided to try "Chaining modals" approach.

The approach seems to work in my case, hovewer I am facing another problem- as I am not using any loop, values are not used as a parameter anymore (vector "all.elements" is not being used).
In my case it is a little bit a problem, because in my original app "elements" to be checked are being selected beforewards and only a part of vector elements should be included in "shinyalert" part.
For this reason I would like to keep "vector of elements" with elements selected beforewards. Elements not included in the vector should be skipped in the chain of modals (in the example element "C" should be skipped, that means "Do you accept element C?" message should not pop up even if elements "A" and "B" had been previously confirmed by a user).
App should proceed from "Do you accept element B?" to "Do you accept element D?" question directly
I was trying to achieve that using ifelse function inside my shinyalert but I have not succeeded.

Is there way to do that? Below the code I wrote using "Chaining modals" approach.

library(shiny)
library(shinyalert)

ui <- fluidPage(
    actionButton("run", "Run")
)

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

observeEvent(input$run, {
    
    all.elements <- c("A", "B", "C", "D", "E")
    selected.elements <- c("A", "B", "D", "E")
    
    shinyalert(
        title = "Do you accept element A?",
        showCancelButton =  TRUE,
        callbackR = function(value) {
            ifelse(value == TRUE,
                   shinyalert(title = "Do you accept element B?",
                              showCancelButton =  TRUE,
                              callbackR = function(value) {
                                  ifelse(value == TRUE, shinyalert(title = "Do you accept element C?",
                                                                   showCancelButton =  TRUE,
                                                                   callbackR = function(value) {
                                                                       ifelse(value == TRUE, shinyalert(title = "Do you accept element D?",
                                             showCancelButton =  TRUE, callbackR = function(value) {
                                                 ifelse(value == TRUE,
                                                        shinyalert(title = "Do you accept element E?",
                                                                   showCancelButton =  TRUE), decision <<- "STOP")}), decision <<- "STOP")}), decision <<- "STOP")}), decision <<- "STOP")}
    )
})
}

shinyApp(ui = ui, server = server)

Another thing I noticed, when I click "OK" quickly, sometimes the consecutive message does not appear. Maybe there is a way to do similar thing using Modals approach built in Shiny, without using the shinyalert library?


Solution

  • Use shinyWidgets::confirmSweetAlert instead. I have noticed this bug in shinyalert chaining before, and you need to ask the author to fix it. Instead, I always use sweetalert from shinyWidgets. Besides, when the call back chain is long, it looks very ugly. It's called the callback hell: http://callbackhell.com/.

    See below how I solve the problem with confirmSweetAlert

    library(shiny)
    library(shinyWidgets)
    
    ui <- fluidPage(
        actionButton("run", "Run")
    )
    
    server <- function(input, output, session) {
        observeEvent(input$run, {
            all.elements <- c("A", "B", "C", "D", "E")
            selected.elements <- c("A", "B", "D", "E")
            confirmSweetAlert(session, "comfirmA", "Do you accept element A")
        })
        
        observeEvent(input$comfirmA, {
            req(input$comfirmA)
            confirmSweetAlert(session, "comfirmB", "Do you accept element B")
        })
        
        observeEvent(input$comfirmB, {
            req(input$comfirmB)
            confirmSweetAlert(session, "comfirmC", "Do you accept element C")
        })
        
        observeEvent(input$comfirmC, {
            req(input$comfirmC)
            confirmSweetAlert(session, "comfirmD", "Do you accept element D")
        })
        
        observeEvent(input$comfirmD, {
            req(input$comfirmD)
            confirmSweetAlert(session, "comfirmE", "Do you accept element E")
        })
    }
    
    shinyApp(ui = ui, server = server)
    

    OK, coming back to your question, how do we do the chain automatically and skip some items.

    library(shiny)
    library(shinyWidgets)
    
    ui <- fluidPage(
        actionButton("run", "Run")
    )
    
    server <- function(input, output, session) {
        all.elements <- c("A", "B", "C", "D", "E")
        selected.elements <- c( "B", "D", "E")
        excluded_index <- which(!all.elements %in% selected.elements)
        comfirm_text <- c(
            "Do you accept element A",
            "Do you accept element B",
            "Do you accept element C",
            "Do you accept element D",
            "Do you accept element E"
        )
        
        observeEvent(input$run, {
            confirmSweetAlert(session, paste0("comfirm", selected.elements[1]), comfirm_text[-excluded_index][1])
        })
    
        lapply(seq_along(selected.elements)[2:length(selected.elements)], function(x) {
            observeEvent(input[[paste0("comfirm", selected.elements[x -1])]], {
                req(input[[paste0("comfirm", selected.elements[x -1])]])
                confirmSweetAlert(
                    session, 
                    paste0("comfirm", selected.elements[x]), 
                    comfirm_text[-excluded_index][x]
                )
            })
        })
    
    }
    
    shinyApp(ui = ui, server = server)
    
    
    1. Pregenerate all text you want to show on each modal.
    2. Find out the index of excluded elements, we will use this to remove items in the the text element.
    3. When button is clicked, we trigger the first selected.elements
    4. Use a loop to create the chain of selected.elements from 2 to end.

    enter image description here