Hello RStudio Community and thank you in advance for reading through my question. ![]()
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!