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!