简体   繁体   中英

Dynamic UI/Server Modules in Shiny Dashboard Based on Inputs in UI

Let's say I have 4 sets of UI/Server modules in 4 different directories ("./X1/Y1/", "./X1/Y2/", "./X2/Y1/", "./X2/Y2/"). I want to load the selected set based on the input in the sidebar.

I tried using source() within dashboardBody() , but I was not successful.

library(shiny)
library(shinydashboard)

# path to modules
in_path <- "C:/a/b/c/"

# ui
ui <- dashboardPage(
  
  dashboardHeader(title = "test"),

  dashboardSidebar(

    br(),
    selectInput('f1', 'Folder 1', choices = c("X1", "X2")),
    helpText(""),
    selectInput('f2', 'Folder 2', choices = c("Y1", "Y2")),
    br(),
    actionButton("load", "Load", icon("thumbs-up"), width = "85%")

  ),

  dashboardBody(
    
    # UI module here from, e.g., "C:/a/b/c/X1/Y2/my_UI.R"
    
  )
)

# server
server <- function(input, output, session) {
  
  # server module here from, e.g., "C:/a/b/c/X1/Y2/my_Server.R"
  
}

shinyApp(ui, server)

As shiny modules are simply functions, I'd source them in the beginning, and use uiOutput to display the differnt modules.

Here's a working example of the general idea (sample module code proudly stolen from the official Shiny documentation ):

<mod1.R>

counterButton <- function(id, label = "Counter") {
   ns <- NS(id)
   tagList(
      actionButton(ns("button"), label = label),
      verbatimTextOutput(ns("out"))
   )
}

counterServer <- function(id) {
   moduleServer(
      id,
      function(input, output, session) {
         count <- reactiveVal(0)
         observeEvent(input$button, {
            count(count() + 1)
         })
         output$out <- renderText({
            count()
         })
         count
      }
   )
}

<mod2.R>

csvFileUI <- function(id, label = "CSV file") {
   ns <- NS(id)
   
   tagList(
      fileInput(ns("file"), label),
      checkboxInput(ns("heading"), "Has heading"),
      selectInput(ns("quote"), "Quote", c(
         "None" = "",
         "Double quote" = "\"",
         "Single quote" = "'"
      ))
   )
}

csvFileServer <- function(id, stringsAsFactors  = TRUE) {
   moduleServer(
      id,
      ## Below is the module function
      function(input, output, session) {
         # The selected file, if any
         userFile <- reactive({
            # If no file is selected, don't do anything
            validate(need(input$file, message = FALSE))
            input$file
         })
         
         # The user's data, parsed into a data frame
         dataframe <- reactive({
            read.csv(userFile()$datapath,
                     header = input$heading,
                     quote = input$quote,
                     stringsAsFactors = stringsAsFactors)
         })
         
         # We can run observers in here if we want to
         observe({
            msg <- sprintf("File %s was uploaded", userFile()$name)
            cat(msg, "\n")
         })
         
         # Return the reactive that yields the data frame
         return(dataframe)
      }
   )    
}

<app.R>

library(shiny)

source("mod1.R")
source("mod2.R")

my_mods <- list("Counter Button" = list(ui = counterButton,
                                        server = counterServer),
                "CSV Uploader" = list(ui = csvFileUI ,
                                      server = csvFileServer))

ui <- fluidPage(
   sidebarLayout(
      sidebarPanel(
         selectInput("mod_sel",
                     "Which Module should be loaded?",
                     names(my_mods))
      ),
      mainPanel(
         uiOutput("content"),
         verbatimTextOutput("out")
      )
   )
)

server <- function(input, output) {
   uuid <- 1
   handler <- reactiveVal()
   
   output$content <- renderUI({
      my_mods[[req(input$mod_sel)]]$ui(paste0("mod", uuid))
   })
   
   observeEvent(input$mod_sel, {
      handler(my_mods[[req(input$mod_sel)]]$server(paste0("mod", uuid)))
      uuid <<- uuid + 1
   })
   
   output$out <- renderPrint(req(handler())())
}

shinyApp(ui, server)

Some Explanation

  1. You put the module code in mod[12].R and it is rather straight forward.
  2. In your main app, you load both(,) source files and for housekeeping reasons, I put both modules functions ( ui and server ) in a list , but this is not strictly necessary, but facilitates future extension.
  3. In your UI you have an uiOutput which renders dynamically according to the selected module.
  4. In your server you put the code to dynamically render the UI and call the respective server function.
  5. The uid construct is basically there to force a fresh render, whenever you change the selection. Otherwise, you may see still some old values whenever you come back to a module which you have rendered already.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM