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

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())
})
}
`

Related

Why I do not get my data under the Load Data Panel?

I am having two tabs in the navigation bar (I am trying to keep it simple, I have more, but won't matter). Now, I get my info tab the way I want it. But when I upload the module for load data, I cannot get it under 'Load Data' tab but rather under the first tab - Info .
Here is a snipped of the code (although I can give access to the repo upon request - https://github.com/gabrielburcea/grwtgolem), it is golem shiny framework and would like to keep it this way.
First, I define app_ui :
app_ui <- function(request) {
tagList(# Leave this function for adding external resources
golem_add_external_resources(),
shinyjs::useShinyjs(),
# Your application UI logic
shinyUI(
shiny::navbarPage(title = div(tags$a(img(src = "www/AZ_SYMBOL_RGB.png", height = "50px"), "Growth Rate Explorer"),
id = "navBar",
theme = "www/style.css",
# collapsible = TRUE,
# inverse = TRUE,
style = "position: relative; top: -30px; margin-left: 10px; margin-top: 5px;"),
header = tags$head(includeCSS("www/style.css")),# sourcing css style sheet
# make navigation bar collapse on smaller screens
windowTitle = "Growth Rate Explorer",
collapsible = TRUE,
shiny::tabPanel("Info", icon = icon("fa-light fa-info"), mod_info_app_ui("info_app_1")),
shiny::tabPanel("Load Data", icon = icon("fa-light fa-database"), mod_load_app_ui("load_app_1"))
)
)
)
}
And then, I define the server_app as:
#' app_server
#'
#' #param input,output,session Internal parameters for {shiny}.
#' DO NOT REMOVE.
#' #import shiny
#' #noRd
app_server <- function(input, output, session){
mod_info_app_server("info_app_1")
mod_load_app_server("load_app_1")
}
To reiterate, I get my load app under the info tab. Why is this happening?
I have tried for the last two days different configuration but nothing helped.
I tried to re-define the app_server with the shiny::callModule(mod_load_server, mod_load_ui_1) and it did not work whatsoever.
For your info: mod_info_app and mod_load_app:
First is mod_info_app that contains html scripts (which I won't provide, these are way too big) but this module defines the ui and server for info tab, just as golem requires:
#'mod_info_app_ui UI Function
#'
#' #description A shiny Module.
#'
#' #param id,input,output,session Internal parameters for {shiny}.
#'
#' #noRd
#'
#' #importFrom shiny NS tagList
mod_info_app_ui <- function(id){
ns <- NS(id)
tagList(
tagList(shiny::tabPanel(title = "Info",
tags$div(
class = "main",
shiny::fluidPage(
htmltools::htmlTemplate("www/welcome_to_growth_rate_explorer.html"),
htmltools::htmlTemplate("www/info_tabs_list.html")
)
)))
)
}
#' mod_info_app_server Server Functions
#'
#' #noRd
mod_info_app_server <- function(id){
moduleServer( id, function(input, output, session){
ns <- session$ns
# Color coding
colorCoding <- reactive({
tagList(
tags$b("Legend"),
tags$p(drawBullet(color = paste(myColors[1], "; border: 1px solid black")), "Adjusted p Value > 0.05"),
tags$p(drawBullet(color = myColors[2]), "0.01 < Adjusted p Value", HTML("≤"), "0.05"),
tags$p(drawBullet(color = myColors[3]), "0.001 < Adjusted p Value", HTML("≤"), "0.01"),
tags$p(drawBullet(color = myColors[4]), "0.0001 < Adjusted p Value", HTML("≤"), "0.001"),
tags$p(drawBullet(color = myColors[5]), "Adjusted p Value", HTML("≤"), "0.0001")
)
})
output$info_colorCoding <- renderUI(colorCoding())
etc, etc,
})
}
Then comes the mod_load_app, with ui and server defined, it is a bit long but just for yourself to make yourself:
#' mod_load_app_ui UI Function
#'
#' #description A shiny Module.
#'
#' #param id,input,output,session Internal parameters for {shiny}.
#'
#' #noRd
#'
#' #importFrom shiny NS tagList
mod_load_app_ui <- function(id) {
ns <- NS(id)
tagList(shiny::tabPanel(
title = "Load Data",
tags$br(),
shiny::sidebarLayout(
shiny::sidebarPanel(
hidden(
actionButton(
inputId = "load_loadNewButton",
icon = icon("arrow-alt-circle-up"),
label = "Upload new data"
)
),
# Load data
div(
id = "load_inputDataSpecifics",
fileInput(
inputId = "load_file",
label = "Data File(s)",
accept = c(".csv", ".xlsx"),
multiple = TRUE
),
uiOutput("load_selectColumnNamesUI"),
uiOutput("load_dayOffsetInput"),
uiOutput("load_loadDataButtonUI")
),
uiOutput("load_warnings"), etc, etc, etc, etc
)
)
)
)
))
}
#' mod_load_app_server Server Functions
#'
#' #noRd
mod_load_app_server <- function(id){
moduleServer( id, function(input, output, session){
ns <- session$ns
observe({
req(results$load_dataType())
if(length(input$load_loadDataButton) ==1){
toggleElement(id = "load_MBPlotFacet", condition = results$load_dataType() ==2)
}
toggleElement(id = "load_excludeIdSelect", condition = input$load_outlierType %in% c(1,3))
toggleElement(id = "load_excludeButton", condition = isTruthy(input$load_excludeReason))
toggleElement(id = "load_excludeDaySelect", condition = input$load_outlierType %in% c(2,3))
})
observe({
req(results$load_dataVolume())
updateSelectInput(session,
inputId= "load_excludeDaySelect",
choices = sort(unique(results$load_dataVolume()$day)))
sortedIds <- list()
for(treatment in levels(results$load_dataVolume()$treatment)){
ids <- unique(results$load_dataVolume()$animal_id[results$load_dataVolume()$treatment == treatment])
sortedIds[[treatment]] <- ids
}
updateSelectInput(session, inputId= "load_excludeIdSelect", choices = sortedIds)
})
output$load_selectColumnNamesUI <- renderUI({
req(results$load_dataInputFile0())
myColumns <- matchColumns(results$load_dataInputFile0())
names <- names(myColumns)
inputIds <- paste0("load_columnName_", names)
tagList(
textInput("load_dayOffset",
label = "Specify how day is defined",
value = "Post-implant"),
helpText("Please check whether the program has detected the right columns"),
lapply(1:length(myColumns), function(i)
selectizeInput(inputId = inputIds[i],
label = names[i],
choices = myColumns[[i]]$options,
selected = myColumns[[i]]$guess))
)
}) etc, etc, etc..
}
## To be copied in the UI
# mod_load_app_ui("load_app_1")
## To be copied in the server
# mod_load_app_server("load_app_1")
....... Adding more ----------
If you look, as you can see the body page appears under both tabs regardless of which I choose
Second pic
And the third pic with load data info mixed up with Info Tab:

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

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.

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.