Passing inputs between modules in shinydashboard

Hello RStudio Community and thank you in advance for reading through my question. :slight_smile:

It's a little long-winded, but I've tried to reduce it to a manageable reprex (and there is more documentation here).

Objective

This app uploads an Excel file in one menuItem(), and displays the data in the 2nd menuItem(). The application modules are separated into two components: import_xlsx and display_xlsx.

I based much of the code on the post by RStudio titled, "Communication between modules".

Here is an example excel file.

Import modules

The import_ UI module is below:

library(shiny)
library(shinydashboard)
library(tidyverse)
library(readxl)
library(reactable)

import_xlsx_UI <- function(id) {
  tagList(
    fluidRow(
      column(
        12,
        box(
          width = NULL,
          collapsible = TRUE,
          title = h4(strong("1.0) Import excel file:")),
          shiny::fileInput(
            # xlsx_file -------
            inputId = NS(namespace = id, id = "xlsx_file"),
            label = tags$em(
              "File input",
              tags$a(href = "https://bit.ly/3N1Klgz", "(example)"), ":"
            ),
            accept = c(".xlsx")
          )
        )
      )
    ),
    fluidRow(
      column(
        12,
        box(
          width = NULL,
          collapsible = TRUE,
          title = h4(tags$strong("1.1) Select sheets:")),
          column(
            width = 6,
            tags$em("Select sheet:"),
            selectInput(
              # select_sheets ---------
              inputId = NS(
                namespace = id,
                id = "select_sheets"
              ),
              label = " ",
              choices = ""
            )
          ),
        )
      )
    ),
    fluidRow(
      box(
        width = 12,
        collapsible = TRUE,
        title = h4(tags$strong("1.2) Select columns:")),
        selectizeInput(
          # select_columns ---------
          inputId = NS(
            namespace = id,
            id = "select_columns"
          ),
          label = "Choose columns:",
          multiple = TRUE,
          choices = ""
        )
      ),
    )
  )
}

The server import_ module is below:

import_xlsx_Server <- function(id) {
  moduleServer(id, function(input, output, session) {

    # xlsx_sheets() ----
    xlsx_sheets <- reactive({
      req(input$xlsx_file)
      xlsx_sheets <- readxl::excel_sheets(path = input$xlsx_file$datapath)
      return(xlsx_sheets)
    })

    # sheet drop-down options -----
    observe({
      inFile <- input$xlsx_file
      if (is.null(inFile)) {
        return(NULL)
      } else {
        xlsx_sheets <- readxl::excel_sheets(path = input$xlsx_file$datapath)
        updateSelectInput(session, "select_sheets", choices = xlsx_sheets)
      }
    })

    # columns drop-down options -----
    observeEvent(eventExpr = input$select_sheets, handlerExpr = {
      req(input$xlsx_file)
      worksheet_data <- readxl::read_excel(
        path = input$xlsx_file$datapath,
        sheet = input$select_sheets
      )

      worksheet_cols <- select(
        .data = worksheet_data,
        !starts_with("...")
      )

      updateSelectizeInput(session, "select_columns",
        choices = names(worksheet_cols)
      )
    })

    # list of imported items 
    return(
      list(
        # xlsx_sheets
        xlsx_sheets = reactive({
          req(input$xlsx_file)
          xlsx_sheets <- excel_sheets(input$xlsx_file$datapath)
        }),
        # xlsx_columns
        xlsx_columns = reactive({
          req(input$xlsx_file)

          worksheet_data <- readxl::read_excel(
            path = input$xlsx_file$datapath,
            sheet = input$select_sheets
          )

          worksheet_cols <- select(
            .data = worksheet_data,
            !starts_with("...")
          )

          xlsx_columns <- names(worksheet_cols)
        }),
        
        # xlsx_data
        xlsx_data = reactive({
          req(input$xlsx_file)
          req(input$select_sheets)
          req(input$select_columns)

          worksheet_data <- readxl::read_excel(
            path = input$xlsx_file$datapath,
            sheet = input$select_sheets
          )

          selected_cols <- select(
            .data = worksheet_data,
            all_of(input$select_columns)
          )
        })
      )
    )
  })
}

Import app demo

You can demo this portion of the app using the demo below:

import_app_demo <- function() {
    # ui
  ui <- dashboardPage(
    dashboardHeader(title = "import xlsx data"),
    dashboardSidebar(
      sidebarMenu(
        menuItem("Import excel file",
          tabName = "import",
          icon = icon("file")
        )
      )
    ),
    dashboardBody(
      tabItems(
        tabItem(
          tabName = "import",
          import_xlsx_UI("excel_import")
        )
      )
    )
  )
  # server
  server <- function(input, output) {
    excel_data <- import_xlsx_Server(id = "excel_import")
  }
  
  # run 
  shinyApp(ui = ui, server = server)
}
import_app_demo()

Display modules

The display module uses two verbatimTextOutput()s and a reactableOutput().

display_xlsx_UI <- function(id) {
  tagList(
    fluidRow(
      column(
        12,
        box(
          width = NULL,
          collapsible = TRUE,
          title = h4(strong("2.0) Display sheets")),
          shiny::verbatimTextOutput(
            # display_sheets ---------
            outputId = NS(
              namespace = id,
              id = "display_sheets"
            )
          )
        )
      )
    ),
    fluidRow(
      column(
        12,
        box(
          width = NULL,
          collapsible = TRUE,
          title = h4(strong("2.1) Display columns")),
          shiny::verbatimTextOutput(
            # display_columns ---------
            outputId = NS(
              namespace = id,
              id = "display_columns"
            )
          )
        )
      )
    ),
    fluidRow(
      column(
        12,
        box(
          width = NULL,
          collapsible = TRUE,
          title = h4(tags$strong("2.2) Display table")),
          reactable::reactableOutput(
            # display_table ---------
            outputId = NS(
              namespace = id,
              id = "display_table"
            )
          )
        ),
      )
    )
  )
}

This module displays the sheets, columns, and table imported from the first tab.

display_xlsx_Server <- function(id, xlsx_sheets, xlsx_columns, xlsx_data) {
    
  moduleServer(id, function(input, output, session) {
      
    observeEvent(excel_data$xlsx_sheets(), {
      # display_sheets ---------
      output$display_sheets <- shiny::renderPrint({
        print(excel_data$xlsx_sheets())
      })
    })

    observeEvent(excel_data$xlsx_columns(), {
      # display_columns ---------
      output$display_columns <- shiny::renderPrint({
        print(excel_data$xlsx_columns())
      })
    })

    observeEvent(excel_data$xlsx_data(), {
     # display_table -----------
      output$display_table <- reactable::renderReactable({
        reactable(
          data = excel_data$xlsx_data(),
          defaultPageSize = 10,
          resizable = TRUE,
          highlight = TRUE,
          compact = TRUE,
          height = 350,
          wrap = FALSE,
          bordered = TRUE,
          searchable = TRUE,
          filterable = TRUE
        )
      })
    })
  })
}

Display demo app

Below I created a demo app with the import_ and display_ modules. The import_xlsx_Server() creates the excel_data list, and this is used in the display_xlsx_Server() function for the verbatimTextOutput()s and the reactableOutput() in the display_xlsx_UI().

diplay_app_demo <- function() {
  # ui ---------
  ui <- dashboardPage(
    dashboardHeader(
      title = "display xlsx data"
    ),
    dashboardSidebar(
      sidebarMenu(
        menuItem("Import excel file",
          tabName = "import",
          icon = icon("file")
        ),
        menuItem("Display excel data",
          tabName = "display",
          icon = icon("table")
        )
      )
    ),
    dashboardBody(
      tabItems(
        tabItem(
          tabName = "import",
          import_xlsx_UI("excel_import")
        ),
        tabItem(
          tabName = "display",
          display_xlsx_UI("excel_display")
        )
      )
    )
  )
  # server ---------
  server <- function(input, output) {
      
    excel_data <- import_xlsx_Server(id = "excel_import")

    display_xlsx_Server(
      id = "excel_display",
      xlsx_sheets = excel_data$xlsx_sheets(),
      xlsx_columns = excel_data$xlsx_columns(),
      xlsx_data = excel_data$xlsx_data()
    )
  }
  # run -----
  shinyApp(ui = ui, server = server)
}
diplay_app_demo()

Error

The application gives the following error:

Warning: Error in eval_tidy: object 'excel_data' not found
  [No stack trace available]
Warning: Error in eval_tidy: object 'excel_data' not found
  [No stack trace available]
Warning: Error in eval_tidy: object 'excel_data' not found
  [No stack trace available]

I am assuming this error is coming from the excel data not having any values because I haven't imported an excel file yet, so does this mean I need to wrap the entire module in validate()?

Any help would be greatly appreciated!

This topic was automatically closed 54 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.