shiny icon indicating copy to clipboard operation
shiny copied to clipboard

create a dynamic UI using tabsetPanel(type = "hidden") inside modules

Open HAHasani opened this issue 3 years ago • 6 comments

Dear shiny team,

I'm wondering if there is a way I can chose between ui-tabs inside modules. Currently, no error msg is triggered but nothing is happening (files are not uploaded); if I remove the tabset part, the app works normaly and the feedback functions work as expected (without the if condition). could you pls help? thank you

the module's ui is:

mod_ui <- function(session_id){
  useShinyFeedback(),useShinyjs(),
  fluidRow(
    h4("Input files"),br(),
           fileInput(NS(session_id, "files"), "File:", buttonLabel = "Upload", multiple = F, placeholder = "xlsx table"), 
           tabsetPanel(id = NS(session_id, "hidden_tabs"), 
                       type = "hidden",
                       #--------dynamic (error msg or default)
                       tabPanel("err_BC", hr(), div(strong(textOutput(NS(session_id, "msg"))), style = "color:red", align = "center"), hr()),
                       #--------default
                       tabPanel("dflt", h4("dflt"), tableOutput(NS(session_id, "msg")))
  ))
}

while the server is:

mod_Server <- function(session_id){
  moduleServer(session_id, function(input, output, session){
    data = reactive(
      {
        req(input$files)
        ext = tools::file_ext(input$files$datapath)
        feedbackDanger("files", ext!="xlsx", "Incorrect input file! please provide xlsx sheet!")
        req(ext=="xlsx", cancelOutput = T)
        df = input$files
})
   if(!is.data.frame(data()$files)){
        updateTabsetPanel(inputId = "hidden_tabs", selected = "err_BC")
        output$msg = renderText({data()})
    }else{        
      updateTabsetPanel(inputId = "hidden_tabs", selected = "preview")
      output$msg = renderText({data()$datapath})
    }
  })
}

HAHasani avatar Mar 17 '22 16:03 HAHasani

On the server, reactive() is not the correct function here, rather you want observe(). I think you'll find these videos helpful:

https://www.rstudio.com/resources/shiny-dev-con/reactivity-pt-1-joe-cheng/ https://www.rstudio.com/resources/shiny-dev-con/reactivity-pt-2/

jcheng5 avatar Mar 17 '22 17:03 jcheng5

Thank you, and nice talks btw, but it is still not working for me, I think according to your scale my shiny skills are about 3. I'm using reactive because this is my staring point, if input file is:

  • invalid: then nothing else should work (but feedback relies on something to be excuted after reactive to be shown). So inside reactive I'm adding functions that do sanity checks
  • valid: another set of functions will be excuted and a plot is also generated

my plan is, have a hidden tabs to represent these two cases, since the layout of the page will differ accordingly. How would you recommend to impliment it? could you also confirm that the namespace is set correctly here? I fear it all goes down to shiny not really picking on the right id of the tabs for the session..! thank you

HAHasani avatar Mar 17 '22 18:03 HAHasani

OK, I looked a little more closely. First off, in your UI, you should not have two outputs with the same ID (NS(session_id, "msg")). Second, rather than two different tabs that you switch between, I think you should use a single uiOutput that internally chooses between two cases.

mod_ui <- function(session_id){
  useShinyFeedback(),useShinyjs(),
  fluidRow(
    h4("Input files"),
    br(),
    fileInput(NS(session_id, "files"), "File:", buttonLabel = "Upload", multiple = F, placeholder = "xlsx table"),
    uiOutput(NS(session_id, "ui"))
  ))
}
mod_Server <- function(session_id){
  moduleServer(session_id, function(input, output, session){
    is_data_valid <- reactive({
      is.data.frame(input$files) && isTRUE(tools::file_ext(input$files$datapath) == "xlsx")
    })
    data <- reactive({
      req(is_data_valid())
      # read the excel files or whatever
    })

    output$ui <- renderUI({
      if (is_data_valid()) {
        tableOutput(session$ns("tbl"))
      } else {
        tags$strong("Error or whatever")
      }
    })

    output$tbl <- renderTable({
      data()
    })
  }
}

(I'm assuming here that you're going to do much more than simply display a red error, if that's not the case then you should simply throw an error like stop("Incorrect input file") from inside of renderTable)

jcheng5 avatar Mar 17 '22 20:03 jcheng5

Hi and thank you very much, your answers are helping alot. The issue with uiOutput/renderUI they are meant for dynamic "input" ui; what I'm trying to achive is a dynamic "output", i.e. either an error message or a new fluidRow containing sidebarLayout. Therefore I'm trying to get the tabsetPanel working; for whatever reason once I add the tabsetPanel code to the ui, uploading files doesn't work anymore, i.e. I can click the upload button, chose the file, but nothing is shown and no indication the file was uploaded. Thanks again

HAHasani avatar Mar 17 '22 22:03 HAHasani

I still stand by my solution over your approach, but what you're describing sounds like a JavaScript error is being thrown? Do you see an error in the browser's JavaScript console?

jcheng5 avatar Mar 18 '22 18:03 jcheng5

No error is shown. I also tried to do some printing to the console but nothing is printed. Following your recommendation I'll give renderUI another shot, although my first try gave the same results as with tabsetPanel with the exception that the file is uploaded.

HAHasani avatar Mar 18 '22 22:03 HAHasani