Shiny Module for Displaying a Table - r

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)

Related

Passing reactives between Shiny modules to get dynamic updates

I'm new to R and Shiny, and I'm writing a Shiny app that allows the user to create/read/update/delete records in a MariaDB database. When the user adds a record, I want the UI to display the updated contents of the table.
It works if all the code is in a single app.R, but when I break it into Shiny modules (saveNew and displaytbl), I can't get the datatable to automatically update, probably because I haven't figured out the correct reactive 'plumbing' that I need to pass data between modules.
app.R:
library(shiny)
library(dplyr)
library(DT)
ui <- fluidPage(
fluidRow(
saveNewUI("saveNew")),
fluidRow(
displaytblUI("displaytbl")
)
)
server <- function(input, output, session) {
# Load initial contents from database
tibl <- reactiveVal(loadSites())
# Display table
observeEvent(tibl(), { # without observe(), can't access tibl outside reactive context
displaytblServer("displaytbl", tibl())
})
# Save a new record to the table, and update the tibl reactiveVal
tibl(saveNewServer("saveNew"))
}
shinyApp(ui, server)
displaytbl.R:
library(shiny)
library(DT)
displaytblUI <- function(id) {
ns <- NS(id)
tagList(
DT::dataTableOutput(ns("datatable"))
)
}
displaytblServer <- function(id, datatable) {
moduleServer(id, function(input, output, session) {
observeEvent(datatable, {
output$datatable <- renderDataTable(datatable())
})
})
}
saveNew.R:
library(shiny)
saveNewUI <- function(id) {
ns <- NS(id)
tagList(
textInput(ns("siteName"), "Add a site"),
actionButton(ns("btnSave"), "Save"),
)
}
saveNewServer <- function(id) {
moduleServer(id, function(input, output, session) {
observeEvent(input$btnSave, {
saveSite(input$siteName)
})
# Return the updated database table
reactive(loadSites())
})
}
database.R (helper functions):
loadSites <- function() {
query <- "SELECT * FROM names"
res <- as_tibble(dbGetQuery(pool, query))
}
saveSite <- function(siteName) {
query <- paste0("INSERT INTO names (name) VALUES (?)")
params <- list(siteName)
dbExecute(pool, query, params)
}
Expecting the datatable to update when the user clicks the Save button. The database gets updated, but the datatable in the UI does not (until I reload/refresh the app).
After incorporating suggestions from #remko-duursma, I was able to get rid of my error by removing the observeEvent() from my app server(), and also passing the tibl reactiveVal as an argument to saveNewServer(), where it's used to update the database. My working code is below.
app server function:
server <- function(input, output, session) {
# Load initial contents from database
tibl <- reactiveVal(loadSites())
# Display table
displaytblServer("displaytbl", tibl)
saveNewServer("saveNew", tibl)
}
saveNewServer function:
saveNewServer <- function(id, tibl) {
moduleServer(id, function(input, output, session) {
observeEvent(input$btnSave, {
saveSite(input$siteName)
tibl(loadSites())
})
})
}
displaytblServer function:
displaytblServer <- function(id, datatable) {
moduleServer(id, function(input, output, session) {
output$datatable <- renderDataTable(datatable())
})
}

Creating navbarMenu and tabPanel using shiny modules

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)

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)

Shiny Modules not working with renderUI

I am using renderUI to optionally present a Table or Plot based on user selection of the visualization option. I am also using Shiny modules to present the same thing on multiple tabs. While I have gotten Shiny modules to work wonderfully in another app, I am struggling to get it to work with renderUI.
Here is a minimal piece of code that I came up with that shows the problem where nothing gets displayed on either tabs:
myUI <- function(id) {
ns <- NS(id)
fluidRow(
uiOutput(ns('myFinalText'))
)
}
ui <- fluidPage(
tabBox(id = 'myBox', width = 12,
tabPanel('Tab1',
fluidRow(
myUI('tab1')
)),
tabPanel('Tab2',
fluidRow(
myUI('tab2')
))
)
)
myTextFunc <- function(input, output, session, text) {
output$myFinalText <- renderUI({
output$myText <- renderText({text})
textOutput('myText')
})
}
server <- function(input, output, session) {
callModule(myTextFunc, 'tab1', session = session, 'Hello Tab1')
callModule(myTextFunc, 'tab2', session = session, 'Hello Tab2')
}
shinyApp(ui = ui, server = server)
Any thoughts on what else I should be doing to make this work?
Replacing the Shiny module UI function and server functions as follows makes it work fine.
myUI <- function(id) {
ns <- NS(id)
fluidRow(
textOutput(ns('myFinalText'))
)
}
myTextFunc <- function(input, output, session, text) {
output$myFinalText <- renderText({
text
})
}
You can get the namespace from the session object. Change myTextFunc in the initial app like this:
myTextFunc <- function(input, output, session, text) {
ns <- session$ns
output$myFinalText <- renderUI({
output$myText <- renderText({text})
textOutput(ns('myText'))
})
}
You shouldn't call output$ function from another output$ function - it's against Shiny design patterns.
output$myFinalText <- renderUI({
output$myText <- renderText({text})
textOutput(ns('myText'))
})
If you want to know, why it is very bad practice, watch Joe Cheng tutorial about 'Effective reactive programming' from this site: https://www.rstudio.com/resources/webinars/shiny-developer-conference/.
You should use rather reactiveValues or reactive expressions instead. What exactly you should use is dependent from what do you want to achieve, so it's hard to say without detailed example, but according to Joe Cheng everything can be accomplished without nesting outputs or observers.
Sorry for answering my own question...but for others looking for a similar solution, this may be of help.
Here is how I solved for the need to inherit Shiny module namespace on the server side to dynamically render UI. IF there is a better way to solve, please comment or post.
tab1NS <- NS('tab1')
tab2NS <- NS('tab2')
myUI <- function(ns) {
tagList(
fluidRow(
radioButtons(ns('type'), 'Select Visual:',
choices = c('Table' = 'table',
'Plot' = 'plot'))
),
fluidRow(
uiOutput(ns('myCars'))
)
)
}
ui <- fluidPage(
tabBox(id = 'myBox', width = 12,
tabPanel('Tab1',
fluidRow(
myUI(tab1NS)
)),
tabPanel('Tab2',
fluidRow(
myUI(tab2NS)
))
)
)
myTextFunc <- function(input, output, session, cars, ns) {
getMyCars <- reactive({
if (input$type == 'table') {
output$table <- renderDataTable({datatable(cars)})
dataTableOutput(ns('table'))
} else{
output$plot <- renderPlot({
plot(cars$wt, cars$mpg)
})
plotOutput(ns('plot'))
}
})
output$myCars <- renderUI({
getMyCars()
})
}
server <- function(input, output, session) {
callModule(myTextFunc, 'tab1', session = session,
mtcars[mtcars$am == 1, ], tab1NS)
callModule(myTextFunc, 'tab2', session = session,
mtcars[mtcars$am == 0, ], tab2NS)
}
shinyApp(ui = ui, server = server)
Replacing your functions with this renderUI equivalent also works:
myUI <- function(id) {
ns <- NS(id)
fluidRow(
uiOutput(ns('myFinalText'))
)
}
myTextFunc <- function(input, output, session, text) {
output$myFinalText <- renderUI({
text
})
}
Although this obviously does not capture the complexity of what you are really doing. There's something not right about using output$... and textOutput within the renderUI like that. I don't think that is necessary - you don't actually have to use the textOutput function to include text in your output.
EDIT: It occurs to me that the problem has to do with namespaces and modules. When you do output$myText <- renderText(text), the result ends up in the namespace of tab1 or tab2. For example, try changing your textOutput to
textOutput('tab1-myText')
and watch what happens. I think this is why having output$.. variables in your renderUI is problematic. You can access inputs via callModule and that should take care of any namespace issues.

Resources