rstudio / shiny

Easy interactive web applications with R
https://shiny.posit.co/
Other
5.37k stars 1.86k forks source link

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

Open HAHasani opened 2 years ago

HAHasani commented 2 years ago

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})
    }
  })
}
jcheng5 commented 2 years ago

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/

HAHasani commented 2 years ago

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:

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

jcheng5 commented 2 years ago

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)

HAHasani commented 2 years ago

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

jcheng5 commented 2 years ago

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?

HAHasani commented 2 years ago

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.