Name-spacing is breaking when creating UI elements within nested moduleServers - shiny

I am developing a Shiny application with nested shiny modules, when I define variable UI elements within a nested module server the parent module name space is not inherited correctly. For example, if you had the following
Parent module -> ns = parent
Child module -> ns = child
The UI when inspecting the application would display the name-spacing as 'parent-child-...' however when a UI element is defined from the child servers it is now only 'child-...'. To account for this I tried a hacky solution and it worked by pasting 'parent' in front of the 'child' id when creating the element.
I've created an example to capture this issue.
library(shiny)
# Base UI and server elements -------------------------------------------------
histogramUI <- function(id) {
ns <- NS(id)
tagList(
selectInput(ns("var"), "Variable", choices = names(mtcars)),
numericInput(ns("bins"), "bins", value = 10, min = 1),
plotOutput(ns("hist"))
)
}
histogramServer <- function(id) {
moduleServer(id, function(input, output, session) {
data <- reactive(mtcars[[input$var]])
output$hist <- renderPlot({
hist(data(), breaks = input$bins, main = input$var)
}, res = 96)
})
}
# Button UI and server elements ------------------------------------------------
buttonUI <- function(id) {
ns <- NS(id)
uiOutput(ns("new_btn"))
}
# Server created button
buttonServer <- function(id) {
moduleServer(id, function(input, output, session) {
observe({
req(input$var == "cyl")
output$new_btn <-
renderUI({
div(
actionButton(
# Does work \/\/\/
NS(paste0('test-', id), 'action_button'),
# Doesn't work \/\/\/
# NS(id, 'action_button')
label = "Button test")
)
})
})
observeEvent(input$action_button, {
# Printing the session id and selected var
print(id)
print(input$var)
})
})
}
# Master UI elements
major_piece_of_func_ui <- function(id){
ns <- NS(id)
div(
histogramUI(ns("hist_test_1")),
buttonUI (ns("hist_test_1"))
)
}
major_piece_of_func_serv <-
function(id) {
moduleServer(id, function(input, output, session) {
histogramServer("hist_test_1")
buttonServer ("hist_test_1")
})
}
# Ui and server construction
ui <- fluidPage(
major_piece_of_func_ui('test')
)
server <- function(input, output, session) {
major_piece_of_func_serv('test')
}
shinyApp(ui, server)
I am very open to the fact that I may be going about this in the completely wrong way and am open to alternative solutions that at a minimum hold the following constraints:
Constraints:
Withhold the structure of nested modules
Withhold the ability to create UI elements within child module servers
Cheers,
Aidan

For a non-nested module, NS(id) is equivalent to session$ns in the server part. But not for a nested module. For a nested module, use session$ns, it returns the namespacing function with the composed namespace.

Related

How can I send and update reactive values from within a R Shiny module?

I'm trying to write a module that gets a reactive dataframe as an input and allows the user to manipulate it (for the sake of the Minimal Reproducible Example, to add to the table a single row)
Initially, the data that being passed to the module from the main app is some default dataframe (hard coded in the MRE), so the module is always initiated with data.
In addition, I also want to allow the user to manipulate that data from outside the module (for the sake of the MRE, override the dataset with a different, hard-coded dataset).
I cannot make both functionalities in the MRE to work at the same time. At the moment, the update from main app works, but the update from within the module won't work. I found some solutions that would enable the opposite situation.
when trying to add row: no response and no error.
Note 1: The use of modules in the MRE is artificial and not really needed, but it is very much needed in the real app.
Note2: returning a new data frame instead of updating it is not ideal in my case as I would want to allow the user other manipulations, and only after all changes are done, to return the the new data frame.
Minimal Reproducible Example:
library(shiny)
library(tidyverse)
DEFAULT_DATA <- tribble(
~letter, ~number,
"A", 1,
"B", 2,
)
changeDataUI <- function(id) {
ns <- NS(id)
tagList(
tableOutput(ns("tbl"))
,br()
,actionButton(ns("add_row"), 'Add Row')
)
}
changeDataServer <- function(id, data) {
moduleServer(
id,
function(input, output, session) {
observeEvent(input$add_row, {
data <- data() %>% add_row(letter = "C", number = 3)
})
output$tbl <- renderTable(data())
}
)
}
ui <- fluidPage(
titlePanel("MRE App")
,fluidRow(column(6, actionButton("change_dataset", "Change Dataset")))
,fluidRow(column(6, changeDataUI("some_id")))
)
server <- function(input, output) {
glob_rvs <- reactiveValues(data = DEFAULT_DATA)
observeEvent(input$change_dataset, {
glob_rvs$data <- tribble(
~letter, ~number,
"D", 4,
"E", 5,
)
})
changeDataServer(id = "some_id", data = reactive(glob_rvs$data))
}
shinyApp(ui = ui, server = server)
With R, you typically want your modules to act as functional as possible. This, as you point out allows you to better reason about your app. I would have your module return the rows to be added and then have your top level app append them. Otherwise you module is essentially causing side effects. Also, this way your top level app (or another module) could coordinate multiple manipulations. The module could still show the data
Example implementation for module server:
changeDataServer <- function(id, data) {
moduleServer(
id,
function(input, output, session) {
additionalRows <- reactiveVal()
observeEvent(input$change, {
additionalRows(
data.frame(letter = sample(letters, 1) , number = runif(1, 0, 10))
)
})
output$tbl <- renderTable(data())
# return reactive with additional rows to allow to be merged at top level
additionalRows
}
)
}
Then update the server (also changed the input for the upload handler to match the UI (input$uploaded_data not input$uploaded_scheme)
server <- function(input, output) {
glob_rvs <- reactiveValues(data = DEFAULT_DATA)
observeEvent(input$uploaded_data, {
uploaded_data <- read_csv(input$uploaded_data$datapath)
glob_rvs$data <- uploaded_data
})
newRows <- changeDataServer(id = "some_id", data = reactive(glob_rvs$data))
observe({
glob_rvs$data <- bind_rows(glob_rvs$data, newRows())
}) %>%
bindEvent(newRows())
}
What you want to do here is to pass you reactiveValues object as an argument of your module server.
I advise you read this article about how to communicate data between modules
library(shiny)
library(tidyverse)
DEFAULT_DATA <- tribble(
~letter, ~number,
"A", 1,
"B", 2,
)
changeDataUI <- function(id) {
ns <- NS(id)
tagList(
tableOutput(ns("tbl"))
,br()
,actionButton(ns("change"), 'Add Row')
)
}
changeDataServer <- function(id, glob_rvs) {
moduleServer(
id,
function(input, output, session) {
observeEvent(input$change, {
print(glob_rvs$data)
glob_rvs$data <- glob_rvs$data %>% add_row(letter = "C", number = 3)
})
output$tbl <- renderTable(glob_rvs$data)
}
)
}
ui <- fluidPage(
titlePanel("MRE App")
,fluidRow(column(6,
fileInput("uploaded_data",
"would ypu like to upload your own data?",
multiple = FALSE,
accept = c(".csv"))))
,fluidRow(column(6, changeDataUI("some_id")))
)
server <- function(input, output) {
glob_rvs <- reactiveValues(data = DEFAULT_DATA)
observeEvent(input$uploaded_data, {
uploaded_data <- read_csv(input$uploaded_scheme$datapath)
glob_rvs$data <- uploaded_data
})
changeDataServer(id = "some_id", glob_rvs = glob_rvs)
}
shinyApp(ui = ui, server = server)
Following the solution by #Marcus, here is a working version that is consistent with the last version of post:
library(shiny)
library(tidyverse)
DEFAULT_DATA <- tribble(
~letter, ~number,
"A", 1,
"B", 2,
)
changeDataUI <- function(id) {
ns <- NS(id)
tagList(
tableOutput(ns("tbl"))
,br()
,actionButton(ns("add_row"), 'Add Row')
)
}
changeDataServer <- function(id, data) {
moduleServer(
id,
function(input, output, session) {
additionalRows <- reactiveVal()
observeEvent(input$add_row, {
additionalRows(
data.frame(letter = sample(letters, 1) , number = runif(1, 0, 10))
)
})
output$tbl <- renderTable(data())
# return reactive with additional rows to allow to be merged at top level
additionalRows
}
)
}
ui <- fluidPage(
titlePanel("MRE App")
,fluidRow(column(6, actionButton("change_dataset", "Change Dataset")))
,fluidRow(column(6, changeDataUI("some_id")))
)
server <- function(input, output) {
glob_rvs <- reactiveValues(data = DEFAULT_DATA)
observeEvent(input$change_dataset, {
glob_rvs$data <- tribble(
~letter, ~number,
"D", 4,
"E", 5,
)
})
newRows <- changeDataServer(id = "some_id", data = reactive(glob_rvs$data))
observe({
glob_rvs$data <- bind_rows(glob_rvs$data, newRows())
}) %>%
bindEvent(newRows())
}
shinyApp(ui = ui, server = server)

Usage of Modules and stratégie du petit r with navbarPage

I try to understand the usage of modules and stratégie du petit r.
I would like to receive feedback if I used the components correctly?
Also I'm interested: Does it make sense to structure an application in the way that each tabPanel is 1 Module? For Example File Upload, Overview, Clustering, and Reporting.
For this, I built a small application.
A navBarpage with 2 panels. For each tabPanel one module exists which creates the UI and contains the server logic for the panel.
tabPanel 1 / Module "something"
numeric Input -> user can choose between 1,2,3
action button. When pressed the panel View changes.
tabPanel 2 / Module "happend"
shows the chosen number
app_ui (UI-structure and calls module UI)
server_ui (calls Module Servers)
mod_something (Numberinput and actionButton to change panel)
mod_happend ( Displays number)
`
app_ui
app_ui <- function(request) {
tagList(
# Leave this function for adding external resources
golem_add_external_resources(),
# Your application UI logic
fluidPage(
navbarPage("navBarPage",
id = "navBarID",
tabPanel("title1",
mod_something_ui("something_1")),
tabPanel("title2",
mod_happend_ui("happend_1")),
)
)
)
}
app_server
app_server <- function(input, output, session) {
# Your application server logic
r <- reactiveValues()
#mod_Navigation_server("navigation1",r)
mod_something_server("something_1", r = r,parent = session)
mod_happend_server("happend_1",r)
#?moduleServer
}
mod_something
mod_something_ui <- function(id){
ns <- NS(id)
tagList(
selectInput(inputId = ns("numberInput"), label = "choose", choices = c(1,2,3)),
actionButton(inputId = ns("doSomething"),label = "Change Page!"),
)
}
mod_something_server <- function(id,r,parent){
moduleServer(id, function(input, output, session){
ns <- session$ns
observe({
r$numberInput <- input$numberInput
})
observeEvent(eventExpr = input$doSomething,
updateNavbarPage(session = parent, inputId = "navBarID", selected = "title2"))
})
}
mod_happend
mod_happend_ui <- function(id){
ns <- NS(id)
tagList(
p("the Number is"),
textOutput(ns("choosenNumber")),
)
}
#' happend Server Functions
#'
#' #noRd
mod_happend_server <- function(id,r){
moduleServer( id, function(input, output, session){
ns <- session$ns
number <- reactive(r$numberInput)
output$choosenNumber <- renderText(number())
})
}
`

Call for input inside moduleServer

I'm learning Shiny modules. And I'm stuck in a very silly thing: I don't know how to call an input inside moduleServer. In this reprex, the table does not show, I think its because the getInput argument is not properly used in the server. Here's a reprex:
library(shiny)
library(DT)
tablaResumen <- function(id, getInput, tabla1, tabla2) {
moduleServer(id, function(input, output, session) {
output$table <- renderDT({
if(getInput == FALSE){
tabla <- tabla1
}else{
tabla <- tabla2
}
DT::datatable(tabla, escape = FALSE, rownames = FALSE)
})
})
}
ui <- fluidPage(
checkboxInput("input1", label = "Change table"),
DTOutput("table1")
)
server <- function(input, output, session) {
tablaResumen("table1", input$input1, mtcars, iris)
}
shinyApp(ui, server)
library(shiny)
library(DT)
tablaResumen <- function(id, parent_in, get, tabla1, tabla2) {
moduleServer(id, function(input, output, session) {
output$mytable <- renderDT({
if(parent_in[[get]] == FALSE){
tabla <- tabla1
}else{
tabla <- tabla2
}
DT::datatable(tabla, escape = FALSE, rownames = FALSE)
})
})
}
tablaResumenUI <- function(id) {
ns <- NS(id)
DTOutput(ns("mytable"))
}
ui <- fluidPage(
checkboxInput("input1", label = "Change table"),
tablaResumenUI("table")
)
server <- function(input, output, session) {
tablaResumen("table", parent_in = input, "input1", mtcars, iris)
}
shinyApp(ui, server)
Things are a little tricky here.
To render the table, you must put the DTOutput under the same namespace as your mod server. The way we usually do it is by creating a mod UI function and use NS to wrap the id to create the namespace.
You module is depend on a reactive input value input$input1, but the server function itself is not reactive. This means if you provide it as an argument for the mod function, it will be run only one time, so getInput will never be changed after the app is initialized. It becomes a fixed value. To get the reactive value of input1, you need to provide the parent input as an argument as access from there.

rShiny Looping on ui filter conditions

I am trying to create a dashboard in rShiny which follow the following steps
Select a parameter
Filter data from a source table for this parameter
Create a list of this filtered data for one of the column
Iterate over this list to display graphs etc...
I have tried various options for making this work but the communication between ui and server is not happening as expected
I have created a setup as below fot testing
library(shiny)
df_mtcars <- mtcars
df_mtcars <- cbind(CarName = rownames(df_mtcars), df_mtcars)
df_mtcars$CarName <- sub(" ", "_", df_mtcars$CarName)
select the number of gears
Find the cars with that number of gears
Create a list of these cars
Display the data for each of the car by using loop. Loop is needed as other output types like graphs can be latter added
simpUI <- function(id) {
tagList(tableOutput(NS(id, "dat_output"))
numericInput(NS(id, "GearNumber"), "Gear Numbers", 3),
lapply(seq(1, length(v_lst_CarName), by = 1), function(i) {
v_CarName = v_lst_CarName[i]
v_obj_CarName = paste0('sp_cars_', v_CarName)
tableOutput(NS(id, v_obj_CarName))
}))
}
simpServer <- function(id) {
moduleServer(id, function(input, output, session) {
output$dat_output <- renderTable(df_mtcars)
v_lst_CarName <-
reactive(df_mtcars[GearNumber == input$GearNumber]$CarName)
for (v_CarName in v_lst_CarName)
v_obj_CarName = paste0('sp_cars_', v_CarName)
output$v_obj_CarName <- renderTable(v_obj_CarName)
})
}
ui <- fluidPage(fluidRow(simpUI("cars")))
server <- function(input, output, session) {
simpServer("cars")
}
shinyApp(ui = ui, server = server)
It is better to do server side processing. Try this
library(shiny)
library(ggplot2)
df_mtcars <- mtcars
df_mtcars <- cbind(CarName = rownames(df_mtcars), df_mtcars)
df_mtcars$CarName <- sub(" ", "_", df_mtcars$CarName)
simpUI <- function(id) {
ns <- NS(id)
tagList(tableOutput(ns("dat_output")),
numericInput(ns("GearNumber"), "Gear Numbers", 3),
uiOutput(ns("plotxy")),
tableOutput(ns("v_obj_CarName")),
verbatimTextOutput(ns("mylist")),
plotOutput(ns("myplot"))
)
}
simpServer <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
output$dat_output <- renderTable(head(df_mtcars))
mydf <- reactive(df_mtcars[df_mtcars$gear == input$GearNumber,])
v_lst_CarName <- eventReactive(mydf(), {paste0("sp_cars_",mydf()$CarName)})
output$plotxy <- renderUI({
req(mydf())
tagList(
selectInput(ns("xvar"), label = "X-axis variable", choices = names(mydf()), selected=names(mydf())[2] ),
selectInput(ns("yvar"), label = "Y-axis variable", choices = names(mydf()), selected=names(mydf())[5] )
)
})
output$v_obj_CarName <- renderTable({mydf()})
output$mylist <- renderPrint(list(v_lst_CarName() ))
output$myplot <- renderPlot({
req(input$xvar,input$yvar)
ggplot(mydf(),aes(x=.data[[input$xvar]], y=.data[[input$yvar]])) + geom_point()
})
})
}
ui <- fluidPage(fluidRow(simpUI("cars")))
server <- function(input, output, session) {
simpServer("cars")
}
shinyApp(ui = ui, server = server)

User defined function output in Shiny not in scope

I would like to use a user defined function in Shiny to perform a simple calculation with output two variables. The function I wrote works when it is not part of a shiny app. However when part of a Shiny, the returned object (dfr) is ‘not in scope’. What am I missing?
library(shiny)
# Function ----------------------------------------------------------------
convert <- function(coef_1, coef_2, vec) {
part_1 <- coef_1/(2*sin((vec/2)*pi/180))
part_2 <- 2*(180/pi)*(asin(coef_2/(2*part_1)))
dfr <- data.frame(part_1, part_2)
return(dfr)
}
# End Function ------------------------------------------------------------
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("num", h3("Enter number to convert:"), value = NULL)
),
mainPanel(
verbatimTextOutput("text1", placeholder = TRUE),
verbatimTextOutput("text2", placeholder = TRUE)
)
)
)
server <- function(input, output) {
nums_str <- as.character(input$num)
nums_vector <- as.numeric(unlist(strsplit(nums_str, split = ",")))
convert(1.5, 1.1, nums_vector)
output$text1 <- renderText({
req(input$num)
dfr$part_1
})
output$text2 <- renderText({
req(input$num)
dfr$part_2
})
}
shinyApp(ui = ui, server = server)
When you use inputs, you need to do it in reactive environment, such as reactive(), renderDataTable(), etc.
Here, you need to run your function in a reactive() and then call it with dfr() in the outputs.
server <- function(input, output) {
dfr <- reactive({
convert(1.5, 1.1, as.numeric(input$num))
})
output$text1 <- renderText({
req(input$num)
dfr()$part_1
})
output$text2 <- renderText({
req(input$num)
dfr()$part_2
})
}
Since this is quite basic stuff with R Shiny, checking some tutorials might be very useful.