In shinydashboard there is the amazing convenience of putting widgets into the menuItem(menuSubItems()) portion of the dashboardSidebar(). But I want the several elements of my UI and Server coded into modules so I can adhere to the golem framework... and I'm not seeing a clear way to do this without creating multiple UI functions for a single module. I've seen the shinydashboard golem example on github and it's too simple of an example that doesn't help.
For example, Is there a way I can do this?
In a module format:
library(shiny)
library(shinydashboard)
### The Sidebar Menu with a Widget Subitem
mod_myAppSidebar_ui<-function(id) {
ns <- NS(id)
tagList(menuItem("Attributes", tabName="ourdata",
textInput("textSearch","SQL Search String", value = "")))
}
### The Dashboard Body output
mod_myAppBody_ui<-function(id) {
ns <- NS(id)
tagList(box(shiny::dataTableOutput(outputId = "OutputData")))
}
mod_myApp_server<-function(input, output, session) {
ns <- session$ns
output$OutputData<-shiny::renderDataTable({
somedata=data.frame(Rows=letters,Indexes=1:length(letters))
somedata[grepl(tolower(input$textSearch),somedata$Rows),]
})
}
### DashboardPage requires separate arguments for the UI elements
ui <- dashboardPage(header = dashboardHeader(title = "Rosetta"),
sidebar = dashboardSidebar(mod_myAppSidebar_ui("MySearch")),
body = dashboardBody(mod_myAppBody_ui("MySearch")))
server <- function(input, output, session) {
callModule(mod_myApp_server, "MySearch")
}
shinyApp(ui,server)
Is there any way to make this kind of thing work? The widget isn't showing up, likely because I don't think the modular framework allows for me to make two different UI elements for one piece of functionality.
Alright, so I got this working... surprisingly didn't take too much. I don't know if the complexity of my app will break this, but for anyone who was hoping to do this, maybe this is helpful:
library(shiny)
library(shinydashboard)
library(DT)
mod_myAppSidebar_ui<-function(id) {
ns <- NS(id)
tagList(menuItem("Attributes", tabName="ourdata",
textInput(ns("textSearch"),"SQL Search String", value = ""),
actionButton(ns("go"),label = "Search")))
}
mod_myAppBody_ui<-function(id) {
ns <- NS(id)
tagList(fluidRow(title = "Data Selected",
box(DT::dataTableOutput(outputId = ns("OutputData")))))
}
mod_myApp_server<-function(input, output, session, r) {
ns <- session$ns
observeEvent( input$go , {
r$textSearch<-input$textSearch
print(r$textSearch)
somedata=data.frame(Rows=letters,Indexes=1:length(letters))
r$chooseData<-somedata[grepl(tolower(input$textSearch),somedata$Rows),]
})
output$OutputData<-DT::renderDataTable(r$chooseData)
}
ui <- dashboardPage(header = dashboardHeader(title = "Rosetta"),
sidebar = dashboardSidebar(mod_myAppSidebar_ui("MySearch")),
body = dashboardBody(mod_myAppBody_ui("MySearch")))
server <- function(input, output, session) {
r<-reactiveValues()
callModule(mod_myApp_server, "MySearch", r)
}
shinyApp(ui,server)
Related
sorry if this is a simple question, but I am quite new to Shiny and even newer to using Modules.
I have a larger application in which one of its pages I use a button to open a Shiny Modal, there are quite a few things going on in this modal, so I made a separate server for it. Inside the modal server are some observeEvent handlers. The problem arises when the user tries to use the modal more than once, as, apparently, opening the modal a second time creates a second instance of its server and then the observeEvents trigger multiple times.
I know that if I use different IDs for the server I can solve this, but I would really like to keep the same ID. In my head, I thought creating a server with the same ID would replace the previous one, but that doesn't seem to be the case. Maybe I just need to delete the previous server when the modal closes(?), I am not sure. Anyway, any help is appreciated.
Here is a reproducible example that shows this behaviour:
Opening the modal a second time and clicking the button makes multiple notifications appear.
modal_server <- function(id){
moduleServer(id,
function(input, output, session){
ns <- session$ns
showModal(modalDialog(actionButton(ns("show_notification"), "Show Notification")))
observeEvent(input$show_notification, {
showNotification("hi")
})
})
}
ui <- fluidPage(
actionButton("show_modal", "Show Modal")
)
server <- function(input, output, session) {
observeEvent(input$show_modal, {
modal_server(id = "modal")
})
}
shinyApp(ui = ui, server = server)
One option to fix your issue would be to move the UI code to the module too and use two observeEvents inside the module server to handle the two events, i.e. showing the modal and showing the notification:
library(shiny)
modal_ui <- function(id) {
ns <- NS(id)
actionButton(ns("show_modal"), "Show Modal")
}
modal_server <- function(id) {
moduleServer(
id,
function(input, output, session) {
ns <- NS(id)
observeEvent(input$show_modal, {
showModal({
modalDialog(
actionButton(ns("show_notification"), "Show Notification")
)
})
})
observeEvent(input$show_notification, {
showNotification("hi")
})
}
)
}
ui <- fluidPage(
modal_ui("modal")
)
server <- function(input, output, session) {
modal_server(id = "modal")
}
shinyApp(ui = ui, server = server)
Consider any Shiny module in which we use session$ns in the server part, e.g. the one below.
We could equivalently use NS(id) instead of session$ns. So why it is recommended to use session$ns? Is there an advantage over NS(id)?
library(shiny)
myModuleUI <- function(id){
ns <- NS(id)
uiOutput(ns("checkbox"))
}
myModuleServer <- function(id){
moduleServer(id, function(input, output, session){
ns <- session$ns
output[["checkbox"]] <- renderUI({
checkboxInput(ns("checkbox"), label = "Check me", value = FALSE)
})
observe({
print(input[["checkbox"]])
})
})
}
ui <- basicPage(
myModuleUI("myModule")
)
server <- function(input, output, session){
myModuleServer("myModule")
}
shinyApp(ui, server)
When I see it correctly, it's due to how the module API was structured before shiny 1.5.0. Until then, you had to write module server functions in the following way:
myModuleServer <- function(input, output, session){
ns <- session$ns
output[["checkbox"]] <- renderUI({
checkboxInput(ns("checkbox"), label = "Check me", value = FALSE)
})
observe({
print(input[["checkbox"]])
})
}
and call a module with:
callModule(myModuleServer, id = "myModule")
With this API, you don't have an id variable for your module server function and you had to resort to session$ns. This is still (erroneously) mentioned in the "Using renderUI within modules" part of the shiny tutorial.
If your renderUI block itself contains inputs/outputs, you need to use ns() to wrap your ID arguments, just like in the examples above. But those ns instances were created using NS(id), and in this case, there’s no id parameter to use. What to do?
The session parameter can provide the ns for you; just call ns <- session$ns. This will put the ID in the same namespace as the session.
The above is not true any more for the new API.
Indeed, in Mastering Shiny, they use NS(id) also in the server part of a module.
However, I'm not sure if there are any subtle differences between using NS(id) and session$ns in the module server with the new API (but considering "Mastering Shiny" uses it, I don't think so).
My app has several screens and I deal with it using tabsetPanel(), hiding the tab headers (I
leave them visible here for debugging) and selecting them using updateTabsetPanel()
It starts on a home screen (coded into mod_home_ui() / mod_home_server())
You push a button to trigger an action, there would be several but I just left one here, called "learn" (coded into mod_learn_ui() / mod_learn_server())
The "learn" module itself contains games, here I left only two games and used the same module functions for both for simplicity.
A reactive value panel_flag, determines which game should be played, here I force it to FALSE, which means game2 should be played.
This last step doesn't work as I expect, while messages show that the code went through the right updateTabsetPanel() call, the expected tab isn't selected, and moreover, the expected text isn't shown on top of the screen.
This looks like a namespacing issue but I don't understand what I did wrong here.
The code below can be copy pasted in one go to run the app and here's a gif of what would happen :
# main ui and server
app_ui <- function() {
tagList(
fluidPage(
title = "My app",
tabsetPanel(
id = "switcher",
#type = "hidden",
selected = "home",
tabPanel("home", mod_home_ui("home_ui")),
tabPanel("learn", mod_learn_ui("learn_ui"))
)
)
)
}
app_server <- function(input, output,session) {
learn <- callModule(mod_home_server, "home_ui")
observeEvent(learn(), {
message("In app_server: observeEvent on learn() to switch to 'learn' panel")
updateTabsetPanel(session, "switcher", selected = "learn")
})
callModule(mod_learn_server, "home_ui", learn = learn)
}
# home module
mod_home_ui <- function(id){
ns <- NS(id)
tagList(
textOutput(ns("some_text")),
actionButton(ns("learn"), "learn")
)
}
mod_home_server <- function(input, output, session){
output$some_text <- renderText("I expect clicking on the above to trigger game2, not game1")
ns <- session$ns
reactive({
res <- req(input$learn)
message(
'In mod_home_server: returning req(input$learn) in mod_home_server to trigger learn()')
res
})
}
# learn module
mod_learn_ui <- function(id){
ns <- NS(id)
tabsetPanel(
id = ns("switcher"),
#type = "hidden",
tabPanel("game1", mod_game_ui(ns("game1_ui"))),
tabPanel("game2", mod_game_ui(ns("game2_ui")))
)
}
mod_learn_server <- function(input, output, session, learn){
ns <- session$ns
panel_flag <- eventReactive(learn(), {
message('In mod_learn_server: eventReactive on learn() to trigger relevant game')
# in reality this would be computed or random
FALSE
})
observeEvent(panel_flag(), {
message('In mod_learn_server: observeEvent on panel_flag()')
if (panel_flag()) {
message('In mod_learn_server: select "game1" panel')
updateTabsetPanel(session, "switcher", selected = "game1")
} else {
message('In mod_learn_server: select "game2" panel')
updateTabsetPanel(session, "switcher", selected = "game2")
}
})
callModule(mod_game_server, "game1_ui")
callModule(mod_game_server, "game2_ui")
}
# game module
mod_game_ui <- function(id){
ns <- NS(id)
tagList(
textOutput(ns("some_text")),
"I expect another line of text above this one"
)
}
mod_game_server <- function(input, output, session){
ns <- session$ns
output$some_text <- renderText("I expect this to be shown")
}
library(shiny)
shinyApp(app_ui, app_server)
callModule(mod_learn_server, "learn_ui", learn = learn)
instead of
callModule(mod_learn_server, "home_ui", learn = learn)
should fix it.
To make sure this doesn't happen again I made a package that tests the consistency of the shiny code, it is designed with the {golem} framework and conventions in mind.
Install with remotes::install_github("moodymudskipper/shinycheck")
This is what I get when I run shinycheck::check_shiny() on my real app (which is slightly different from the above):
shinycheck::check_shiny()
-----------------------------------------------------------------------
Check that all module scripts contain exactly 2 functions named appropriately
-----------------------------------------------------------------------
Check that all module ui functions use ns() or NS() on argument named id/inputId/outputId
-----------------------------------------------------------------------
Check that in ui, module ui functions, named `mod_MODULE_ui` refer to modules which exist and ids fed to them are prefixed with "MODULE_"
-----------------------------------------------------------------------
Check that ns() and NS() are never called in an argument that isn't id/inputId/outputId
-----------------------------------------------------------------------
Check that the module args of callModule are of the form "mod_MODULENAME_server", that there is an R file properly named for "MODULENAME", and that the id argument is prefixed by "MODULENAME_"
* In 'mod_main_server', a call to `callModule` has a module argument `mod_learn_server` and an `id` argument 'home_ui' that is not prefixed by the module name 'learn'
-----------------------------------------------------------------------
Check that modules and module ids mentionned on both ui and server side are consistent
* In 'mod_main_ui' we find the module id 'learn_ui' but we don't find it in 'mod_main_server'
We find :
In 'mod_main_server', a call to callModule has a module argument mod_learn_server and an id argument 'home_ui' that is not prefixed by the module name 'learn'
In 'mod_main_ui' we find the module id 'learn_ui' but we don't find it in 'mod_main_server'
This would have made debugging trivial.
See more at https://github.com/moodymudskipper/shinycheck
I am trying to modularize a complex Shiny app for which I have a conditionalPanel that should only appear given a certain input state.
Before I made everything modular, the input and conditionalPanel were both in ui.R, and I could reference the input using something like this:
conditionalPanel("input.select == 'Option one'", p('Option one is selected'))
Now that I have modularized things, accessing the input is more complicated. I thought the following was the way to do it, but it doesn't quite work. (Here I've combined things into a single standalone script):
library(shiny)
## Module code for 'selectorUI' and 'selector'
selectorUI <- function(id) {
ns <- NS(id)
selectizeInput(inputId = ns('select'),
label = 'Make a choice:',
choices = c('Option one', 'Option two'))
}
selector <- function(input, output, session) {
reactive(input$select)
}
## Main app
ui <- shinyUI(fluidPage(
selectorUI('id1'),
conditionalPanel(condition = "output.selected == 'Option one'", p('Option one is selected.'))
))
server <- shinyServer(function(input, output, session) {
output$selected <- callModule(selector, 'id1')
})
shinyApp(ui = ui, server = server)
I think this should work, but it doesn't - it only works if I make another reference to output$selected in the main ui section:
ui <- shinyUI(fluidPage(
selectorUI('id1'),
textOutput('selected'), ## Adding just this one line makes the next line work
conditionalPanel(condition = "output.selected == 'Option one'", p('Option one is selected.'))
))
Unfortunately of course this has the unwanted effect of rendering the result of textOutput('selected'). I can only guess that the reason this works is because it somehow triggers the reactive in a way that the JavaScript reference alone does not.
Any idea how I should be getting this conditionalPanel to work properly?
Thank you..
EDIT: Turns out not actually a bug: https://github.com/rstudio/shiny/issues/1318. See my own answer below.
But also note that I actually like the renderUI solution given in the accepted answer better than my original conditionalPanel approach.
After calling the module the ID of selectizeInput is id1-select. In javaScript there are two ways of accessing object properties:
objectName.property or objectName['property']
Since there is - in the ID we have to refer to it via string, so the second method is way to go.
The condition in conditionalPanel becomes:
input['id1-select'] == 'Option one'
Full example:
library(shiny)
## Module code for 'selectorUI' and 'selector'
selectorUI <- function(id) {
ns <- NS(id)
selectizeInput(inputId = ns('select'),
label = 'Make a choice:',
choices = c('Option one', 'Option two'))
}
## Main app
ui <- shinyUI(fluidPage(
selectorUI('id1'),
conditionalPanel(condition = "input['id1-select'] == 'Option one'",
p('Option one is selected.'))
))
server <- shinyServer(function(input, output, session) {
})
shinyApp(ui = ui, server = server)
EDIT:
This does work, but doesn't it violate the notion of modularity? You would have to know the code for the module internally calls that input 'select' in order to construct 'id1-select'.
Yes, you're right.
According to this article, the trick you used i.e. assigning a module call to the output$selected and then accessing its value on the client side via output.selected should work but it doesn't. I don't know why...it is maybe a bug. (I have the newest shiny version from github)
The only thing I can think of is to use renderUI instead of conditionalPanel as in the example below:
library(shiny)
## Module code for 'selectorUI' and 'selector'
selectorUI <- function(id) {
ns <- NS(id)
selectizeInput(inputId = ns('select'),
label = 'Make a choice:',
choices = c('Option one', 'Option two'))
}
selector <- function(input, output, session) {
reactive(input$select)
}
## Main app
ui <- shinyUI(fluidPage(
selectorUI('id1'),
uiOutput("dynamic1")
))
server <- shinyServer(function(input, output, session) {
output$dynamic1 <- renderUI({
condition1 <- callModule(selector, 'id1') # or just callModule(selector, 'id1')()
if (condition1() == 'Option one') return(p('Option one is selected.'))
})
})
shinyApp(ui = ui, server = server)
Turns out it actually isn't a bug, just a little tricky. According to Joe Cheng,
Right--we don't, by default, calculate/render output values if they aren't going to be visible. And if we don't calculate them, you can't use them in conditions.
You can change this behavior this by setting an output to calculate every time, you can use this in your server.R (replace outputId with the corresponding value):
outputOptions(output, "outputId", suspendWhenHidden = FALSE)
So to fix the problem with my original example, we only need to add that one line to the server function:
server <- shinyServer(function(input, output, session) {
output$selected <- callModule(selector, 'id1')
outputOptions(output, 'selected', suspendWhenHidden = FALSE) # Adding this line
})
I'm having trouble using the "shinyFilesButton()" and "shinyFilesChoose()" functionality within modules in R shiny.
I believe my issue is related to the namespace functions ("ns()") that effectively create new, unique ids within the modules.
Where do I put the ns() call within the shinyFiles functions? How do I handle this issue on the server side?
I've mocked up an example, with code shown below. The app just selects a file and tells you the info on what you selected. Note that currently no ns() calls are used with any shinyFiles functions. (I've tried wrapping the shinyFilesButton()'s id in the ns(), but then it doesn't match with the shinyFileChoose.)
Currently, this app below will show me files, but only in the root directory. I can't delve deeper into other directories. Additionally, the select button will highlight, but nothing will happen when used.
Edit: I've update the code with an attempt at using the namespaces, per suggestions in the comments. I'm using the ns() function in the shinyFilesButton() call (ui side) and nothing in the server side.
Now I can't see any files when using the file chooser.
Any help?
Below is my code for the app.r file:
#App.R
#Demonstrate issues with ShinyFiles and namesspaces in modules
library(shiny)
library(shinyFiles)
source("shinyFiles_module.R")
server <- function(input, output, session) {
#module Way
callModule(sample,
id="testid",
root_dirs=c(root_on_mac="/Users/Ryan/Desktop/"))
}
ui <- fluidPage(
tagList(
h2("Module Way"),
sample_UI(id = "testid",
label = "shiny file test")
)
)
shinyApp(ui = ui, server = server)
And for the module:
#Sample shinyFiles Module
#trying to get File path using ShinyFiles within a Module
library(shiny)
library(shinyFiles)
#Settings UI function:
# Module UI function
sample_UI <- function(id, label = "Shiny file test") {
# Create a namespace function using the provided id
ns <- NS(id)
#begin UI (wrap all input/ouput in ns() call)
tagList(
strong("Selected Location: "), verbatimTextOutput(ns("file_path")),
shinyFilesButton(
id=ns("get_file_path"),
label="Click Here to Select",
title="Select a file",
multiple= FALSE,
buttonType = "default",
class = NULL)
)
}
# Module server function
sample <- function(input,
output,
session,
root_dirs,
id_value) {
shinyFileChoose(input, id="get_file_path", roots=root_dirs, session=session)
output$file_path <- renderPrint({
parseFilePaths(roots=root_dirs, input$get_file_path)
})
}
Change your module to this and your program works:
library(shiny)
library(shinyFiles)
#Settings UI function:
# Module UI function
sample_UI <- function(id, label = "Shiny file test") {
# Create a namespace function using the provided id
ns <- NS(id)
#begin UI (wrap all input/ouput in ns() call)
tagList(
strong("Selected Location: "), verbatimTextOutput(ns("file_path")),
shinyFilesButton(
id=ns("get_file_path"),
label="Click Here to Select",
title="Select a file",
multiple= FALSE,
buttonType = "default",
class = NULL)
)
}
# Module server function
sample <- function(input,
output,
session,
root_dirs) {
ns <- session$ns
shinyFileChoose(input, id=ns("get_file_path"), roots=root_dirs, session=session)
output$file_path <- renderPrint({
parseFilePaths(roots=root_dirs, input$get_file_path)
})
}
I have the same issue and I resolve it by following Carl's suggestion:
use ns() in ui
remove ns() in server
I am using R3.6.1 and it works. In R3.4 it has problem navigate into the subfolders.
library(shiny)
library(shinyFiles)
#Settings UI function:
# Module UI function
sample_UI <- function(id, label = "Shiny file test") {
# Create a namespace function using the provided id
ns <- NS(id)
#begin UI (wrap all input/ouput in ns() call)
tagList(
strong("Selected Location: "), verbatimTextOutput(ns("file_path")),
shinyFilesButton(
id=ns("get_file_path"),
label="Click Here to Select",
title="Select a file",
multiple= FALSE,
buttonType = "default",
class = NULL)
)
}
# Module server function
sample <- function(input,
output,
session,
root_dirs,
id_value) {
shinyFileChoose(input, id="get_file_path", roots=root_dirs, session=session)
output$file_path <- renderPrint({
parseFilePaths(roots=root_dirs, input$get_file_path)
})
}