Creating navbarMenu and tabPanel using shiny modules - r

I want to create a shiny app that uses navbarMenu() and tabPanel() to display data tables. Instead of writing all of the codes in one app.R file, I plan to use the concept of shiny modules creating R/tabUI.R and R/tabServer.R to generate these tables.
However, I run into an error and cannot figure it out. Any suggestions and help are appreciated!
My code:
### R/tabUI.R
tabUI <- function(id) {
tagList(
navbarMenu("display table",
tabPanel(NS(id, "mtcars table"),
DT::dataTableOutput("table")
)
)
)
}
### R/tabServer.R
tabServer <- function(id) {
moduleServer(id, function(input, output, session){
output$table <- DT::renderDataTable(mtcars)
})
}
### app.R
library(shiny)
ui <- navbarPage("dashboard",
tabUI("table1")
)
server <- function(input, output, session){
tabServer("table1")
}
shinyApp(ui=ui, server=server)
Error:
> runApp()
Error: Navigation containers expect a collection of `bslib::nav()`/`shiny::tabPanel()`s and/or `bslib::nav_menu()`/`shiny::navbarMenu()`s. Consider using `header` or `footer` if you wish to place content above (or below) every panel's contents.

You can't use tagList() inside navbarPage() so you need to remove it from the module.
As a sidenote, you should define ns <- NS(id) at the beginning of the module and then wrap all ids in ns(). In your code, the table id was not wrapped in ns() so it wasn't displayed.
Fixed code:
### R/tabUI.R
tabUI <- function(id) {
ns <- NS(id)
navbarMenu("display table",
tabPanel(ns("mtcars table"),
DT::dataTableOutput(ns("table"))
)
)
}
### R/tabServer.R
tabServer <- function(id) {
moduleServer(id, function(input, output, session){
output$table <- DT::renderDataTable(mtcars)
})
}
### app.R
library(shiny)
ui <- navbarPage("dashboard",
tabUI("table1")
)
server <- function(input, output, session){
tabServer("table1")
}
shinyApp(ui=ui, server=server)

Related

Shiny Module for Displaying a Table

Getting started with shiny, I try to learn how to use the Shiny Module design pattern. As the most simple example, I want to display a dataset without any further interaction.
I wish to organise the UI in tabPanels of a navbarPage. Each panel is independent from each other, (except that all panels use a global database connection objects, but this does not bother me now).
Here is the code for the DT GUI element:
library(shiny)
library(DT)
tabTable <- function(id) {
ns <- shiny::NS(id)
tabPanel(
"Table",
shiny::dataTableOutput(ns("table"))
)
}
This is the server logic. I want it to draw the data table.
srvTable <- function(id, dat) shiny::moduleServer(id,
function(input, output, session) {
output$table <- shiny::renderDataTable({DT::datatable(dat)})
}
)
Now here is the definition of the ui and the server:
ui <- shiny::navbarPage(title="Test",
tabTable(id="iris"),
shiny::tabPanel(title="Scatter")
)
server <- function(input, output, session) {
srvTable(id="iris", dat=iris)
session$onSessionEnded(stopApp)
}
shiny::shinyApp(ui, server)
This app starts, it displays the navigation bar, but it does not show the dataset. Any hint where I put the command for that? The problem for me is that there is no obvious condition that the server module needs to react to. What is to do in this case?
Try this
library(shiny)
library(DT)
tabTable <- function(id) {
ns <- shiny::NS(id)
tabPanel(
"Table",
DTOutput(ns("table"))
)
}
srvTable <- function(id, dat) { shiny::moduleServer(id,
function(input, output, session) {
output$table <- renderDT({DT::datatable(dat)})
}
)}
ui <- shiny::navbarPage(title="Test",
tabTable(id="iris"),
shiny::tabPanel(title="Scatter")
)
server <- function(input, output, session) {
srvTable(id="iris", dat=iris)
session$onSessionEnded(stopApp)
}
shiny::shinyApp(ui, server)

Passing uiOutput between nested modules

In my app, I have two modules (nested) for which I want to pass uiOuput. In the example below, the output named "screen1" (created in the outer module)has to be passed to the inner module where it will be displayed. But It doesn't work..
Passing such uiOutput work from the original shinyApp to one module but I can't make it works between two modules.
innerUI <- function(id){
ns <- NS(id)
tagList(
h4("Inner module"),
uiOutput(ns('displayScreens'))
)
}
inner <- function(input, output, session, params){
output$displayScreens <- renderUI({
params()
})
}
outerUI <- function(id){
ns <- NS(id)
innerUI(ns('test1'))
}
outer <- function(input, output, session){
rv <- reactiveValues(
test = uiOutput("screen1")
)
callModule(inner, 'test1', params= reactive({rv$test}))
output$screen1 <- renderUI({
h4("I am the screen 1 !")
})
}
ui <- fluidPage(
outerUI('test2')
)
server <- function(input, output, session){
callModule(outer, 'test2')
}
shinyApp(ui=ui, server=server)
Your problem seems to be one with namespaces. You are creating uiOutput("screen1") in outer and passing it to inner without a namespace. You could pass the uiOutput("screen1") in a reactive like this:
test <- reactive({
ns <- session$ns
uiOutput(ns("screen1"))
})
callModule(inner, 'test1', params=test)
This should work.

R ShinyApps: Dynamic module import causes app to render wrong tab

I am having a shiny app that is heavily relying on modules. Basically it's a big navbarPage with different menus and tabs where every tab is wrapped as a module. Below I provided a minimal example with 2 tabs, but in reality there are more than 20.
# module 1 ------------------------------------------------
moduleOneUI <- function(id) {
ns = NS(id)
tagList(
h2("module1"),
textOutput(ns("text"))
)
}
moduleOne <- function(input, output, session) {
output$text <- renderText({"one yo"})
}
# module 2 ------------------------------------------------
moduleTwoUI <- function(id) {
ns = NS(id)
tagList(
h2("module2"),
textOutput(ns("text"))
)
}
moduleTwo <- function(input, output, session) {
output$text <- renderText({"two yo"})
}
# main app ------------------------------------------------
ui <- navbarPage(
"dashboard",
navbarMenu(
"#1",
tabPanel(
"mod1",
uiOutput("module_one")
)
),
navbarMenu(
"#2",
tabPanel(
"mod2",
uiOutput("module_two")
)
)
)
server <- function(input, output, session) {
output$module_one <- renderUI({
moduleOneUI("module_one")
})
callModule(moduleOne, "module_one")
output$module_two <- renderUI({
moduleTwoUI("module_two")
})
callModule(moduleTwo, "module_two")
}
shinyApp(ui, server)
As you might see the server function gets very large with this hard coded server function. So I tried to create it in a more dynamic way using a loop:
modules <- list(
module_one = c(ui = moduleOneUI, server = moduleOne),
module_two = c(ui = moduleTwoUI, server = moduleTwo)
)
server <- function(input, output, session) {
for (mod_id in names(modules)) {
module <- modules[[mod_id]]
ui_func <- module$ui
server_func <- module$server
output[[mod_name]] <- renderUI({
ui_func(mod_id)
})
callModule(server_func, mod_id)
}
}
However, this approach fails to work as expected. Now I see the moduleTwo code rendered in my module_one tab:
Does any1 know why this is happening and how I can fix it? I really need a dynamic approach to render all those modules.
The two renderUI expressions aren't evaluated until after the loop completes and mod_id = "module_two".
To get around this, you can create a local scope for each loop iteration:
for (mod_id in names(modules)) {
local({
mod_id <- mod_id
module <- modules[[mod_id]]
ui_func <- module$ui
server_func <- module$server
output[[mod_id]] <- renderUI({
ui_func(mod_id)
})
callModule(server_func, mod_id)
})
}
I seem to have found a solution for that problem. If I define a function that calls the module everything works as expected.
create_tab <- function(mod_id, output) {
module <- modules[[mod_id]]
ui_func <- module$ui
server_func <- module$server
output[[mod_id]] <- renderUI({
ui_func(mod_id)
})
callModule(server_func, mod_id)
}
server <- function(input, output, session) {
lapply(names(modules), create_tab, output = output)
}
However, I have no idea why this is working and the other approach isn't. I assume it has something to do with scoping in R.

Using lapply() in renderUI() in Shiny Module

I am trying to convert a section of code into a Shiny Module, but my renderPlot() functions generated within an lapply() don't seem to be working. I have created a simple example below to demonstrate the issue.
(Note: Here I'm using renderText() calls, but the same behavior applies.)
app_normal.R:
library(shiny)
ui <- fixedPage(
h2("Normal example"),
uiOutput("test")
)
server <- function(input, output, session) {
output$test <- renderUI({
lapply(1:3, function(val) {
fluidRow(column(12,renderText(paste("Line", val))))
})
})
}
shinyApp(ui, server)
app_module.R:
library(shiny)
myModuleUI <- function(id) {
ns <- NS(id)
uiOutput(ns("test"))
}
myModule <- function(input, output, session) {
output$test <- renderUI({
lapply(1:3, function(val) {
fluidRow(column(12,renderText(paste("Line", val))))
})
})
}
ui <- fixedPage(
h2("Module example"),
myModuleUI("test_module")
)
server <- function(input, output, session) {
callModule(myModule, "test_module")
}
shinyApp(ui, server)
All of the div elements are being created, but they just fail to contain the plots/text. How do I properly use the Shiny renderText() or renderPlot() functions within renderUI()/lapply() calls within a module?
It appears that the approach I was taking using the renderText() and renderPlot() functions directly in a renderUI() works fine in the normal case, i.e. when not operating within a Shiny Module. Shiny automatically calls the necessary textOutput() or plotOutput() to generate the HTML. Some how this automatic link is broken when you are performing the same operations within a Shiny Module. I suspect this is due to the mismatch between assigning and referencing items in the output list due to the introduction of the ns() call when assigning outputIds as is done manually in a call to outputPlot() or outputText().
To successfully use renderUI within a Shiny Module, you need to separately call textOutput() and renderText(): textOutput in the lapply() in the renderUI(), and renderText() in an lapply() in an observe(). This allows us to introduce an ns() into the generation of the outputId for the textOutput() call.
Below I've included a refactoring of both app_normal.R and app_module.R that demonstrates the disentanglement of these two calls.
app_normal_observe.R:
library(shiny)
ui <- fixedPage(
h2("Normal example"),
uiOutput("test")
)
server <- function(input, output, session) {
output$test <- renderUI({
lapply(1:3, function(val) {
fluidRow(column(12,textOutput(paste0("line_", val))))
})
})
observe({
lapply(1:3, function(val) {
output[[paste0("line_", val)]] <- renderText(paste("Line", val))
})
})
}
shinyApp(ui, server)
app_module_observe.R:
library(shiny)
myModuleUI <- function(id) {
ns <- NS(id)
uiOutput(ns("test"))
}
myModule <- function(input, output, session) {
output$test <- renderUI({
lapply(1:3, function(val) {
fluidRow(column(12,textOutput(session$ns(paste0("line_", val)))))
})
})
observe({
lapply(1:3, function(val) {
output[[paste0("line_", val)]] <- renderText(paste("Line", val))
})
})
}
ui <- fixedPage(
h2("Module example"),
myModuleUI("test_module")
)
server <- function(input, output, session) {
callModule(myModule, "test_module")
}
shinyApp(ui, server)

Can I generate several uiOutput-s from the same R Shiny module?

Here is my code in R Shiny using modules.
I created a module named MyModule and want to generate two UI elements: selectInput and textInput. This code is just an example - in my real application second element require the result from the first element, so I want to generate them separately.
I don't understand why the second uiOutput doesn't generate the UI element it indended to:
library(shiny)
# Define UI
ui <- shinyUI(fluidPage(MyModuleUI("one")))
# Define server logic
server <- shinyServer(function(input, output, session) {callModule(MyModule, 'one')})
#Here is my UI Module
MyModuleUI <- function(id) {
ns <- NS(id)
fluidRow(
uiOutput(ns('ChooseNumber')),
uiOutput(ns('EnterText'))
)
}
#Here is my server Module
MyModule <- function(input, output, session) {
output$ChooseNumber <- renderUI({
# In my bigger program I need this UI to be generated with some database values,
# thats why it is in the Server part of the Module
ns <- session$ns
selectInput(ns("TheNumber"), label = 'Select a number', c(1,2,3))
})
# Same here
output$EnterText <- renderUI({
ns <- session$ns
textInput(ns('TheText'),label = 'Enter a text:',value = 'ABC')
})
}
# Run the application
shinyApp(ui = ui, server = server)
Thank you!

Resources