Recently, I have made a lot of updates to one of the shinydashboards. Nothing too complex, but now the server script is becoming large and I wanted to modularize the code into sections. One thing is there a lot of user parameters and I wanted to place all of these in on script and source.
Sourced Code
mc_High_parameter <- function(input, output, session) {
output$High <- renderUI({
sliderInput("High", 'High Assumption:',
min = 0, max = .25,value = .1)
})
}
Calling on the Server side
callModule(mc_High_parameter,'High')
Displayed on the UI Side
uiOutput("High")
The App loads but the Slider is not displayed on the UI, I cannot really understand the issue. Any Suggestions?
Shiny modules take some getting used to, especially when working with renderUI. I'd take a look at this GitHub page to help give you a sense of how to put it all together, and also read the Shiny help page on the session object, which is fundamental to modules.
In your specific case, here are a few changes to get it running:
1. You need a UI module, which you hadn't included. This is where you call uiOutput.
mc_UI <- function(id) {
ns <- NS(id)
fluidPage(
uiOutput(ns("High"))
)
}
2. You need to call the UI module within the ui.
ui <- fluidPage(
fluidRow(
mc_UI("High")
)
)
3. You need to use session$ns() in the id of your sliderInput so that the namespace is matched properly from the UI.
mc_High_parameter <- function(input, output, session) {
output$High <- renderUI({
sliderInput(session$ns("High"), "High Assumption:",
min = 0, max = .25,value = .1)
})
}
4. Ensure you call session in your server function.
server <- function(input, output, session) {
callModule(mc_High_parameter,"High")
}
Related
I'm trying to build a simple app with two modes. The user can leave 'non-interactive-mode' by flicking a switch. This generates some new UI elements, and produces a result based on the values in the UI element.
For some operations this works fine, but my if statements throw Warnings at the first time I run the code (Warning: Error in if: argument is of length zero). I think I understand why this is happening (the input doesn't exist the first time the code reads the if block), but can it be worked around in a simple way?
MWE below:
library(shiny)
library(shinyWidgets)
ui <- fluidPage(
materialSwitch(inputId = "interactive", label = "Interactive?",value=FALSE),
uiOutput("on_interactive_only"),
uiOutput("result_output")
)
server <- function(input, output) {
# Non-interactive Result
output$result_output <- renderUI(if(!input$interactive){
(renderTable(data.frame('dummy'=1:3)))})
# If interactive we need another UI element
output$on_interactive_only <- renderUI(if(input$interactive){
numericInput("value_to_specify",'Which Number?',5)})
# And now we need to react to that value
output$result_output <- renderUI(if(input$interactive){
if(input$value_to_specify > 3){
(renderTable(data.frame('dummy'=input$value_to_specify)))}})
}
shinyApp(ui = ui, server = server)
I know that using something like this code:
library(shiny)
ui <- fluidPage(
mainPanel(
textOutput("Query_String")
)
)
server <- function(input, output, session) {
observeEvent(session$clientData$url_search,{
Query <- session$clientData$url_search
output$Query_String <- renderText(Query)
# Long list of operations dependant on the parameters passed in the URL
})
}
shinyApp(ui = ui, server = server)
Can enable me to read parameters into my shiny app via URL queries. However it seems like I basically have to wrap my whole server content into one big observe() or observeEvent() to create the needed reactive context.
Is there any way to avoid this?
To avoid recalculating everything if just a single item changed, one wants to create multiple separate observes instead of just a big one. Long code should be refactored in functions e.g. process_query keeping the server function small, allowing to read the overall structure. Important intermediate results can be refactored in their own reactive values (here query). output$Query_String doesn't need to be nested in another reactive context.
process_query <- function(url) {
# many steps to process the url
return(url)
}
server <- function(input, output, session) {
query <- reactive(process_query(session$clientData$url_search))
output$Query_String <- renderText(query())
}
I've been exploring (**and loving) the golem package for developing modular dashboards with R Shiny. But I'm struggling to wrap my head around how to test a modular dashboard.
For example in the repex below how would I test that if the input$n_rows in the import module is set to 15 that the output in the display module contains 15 rows?
I'd be incredibly grateful for any support on this!
library(shiny)
library(reactable)
library(dplyr)
# Import module UI
mod_import_ui <- function(id){
ns <- NS(id)
fluidRow(
# Allow the user to select the number of rows to view
numericInput(ns("n_rows"),
"Select number of observations",
value = 10)
)
}
# Import module Server
mod_import_server <- function(id){
moduleServer(
id,
function(input, output, session){
data <- reactive({
# Sample the requested number of rows from mtcars and return this to the application server
mtcars %>%
slice_sample(n = input$n_rows)
# [....] # Some complex formatting and transformations
})
return(data)
}
)}
# Display module UI
mod_display_ui <- function(id){
ns <- NS(id)
fluidRow(
reactableOutput(ns("table"))
)
}
# Display module Server
mod_display_server <- function(id, data_in){
moduleServer(
id,
function(input, output, session){
# [....] # Some more transformations and merging with data from other modules
output$table <- renderReactable(reactable(data_in()))
}
)}
app_ui <- function(request) {
tagList(
mod_import_ui("import_1"),
mod_display_ui("display_1")
)
}
app_server <- function(input, output, session) {
data_in <- mod_import_server("import_1")
mod_display_server("display_1", data_in)
}
shinyApp(ui = app_ui, server = app_server)
I would recommend separating the core of the app from the user interface.
The {golem} framework allows building your application inside a R package, which means you can use all tools from package build to document and test your code.
If you follow our guide in engineering-shiny.org/, you will see that we recommend extracting all R code from your "server" part to test it in a vignette, transform it as a regular function, so that you can test it as usual with R packages.
Hence, you ShinyApp only calls internal functions, already documented and tested. With this approach, you can test outputs of different scenarios that can happen in your application. Try different input parameters in a static script and verify outputs, whatever you change in your app in the next steps of your development.
The book gives a lot of advices. If I had to sum them up for a workflow, this would be:
Build the necessary code in an Rmd directly. This allows you to test the operation without having to go through all the necessary clicks. We call it the "Rmd first" method: https://rtask.thinkr.fr/when-development-starts-with-documentation/
Factorize this code into R functions to put as little as possible in the Shiny app itself.
Create your UI part without server (or not too much), just to see what the general appearance looks like
Include your functions in the appropriate places in the app.
Strengthen the code. Reproducible examples, unit tests, documentation, code versioning, ... (This step is better when done in parallel with the code)
As a complement to Sebastien's answer, I would like to point that starting with {shiny} v 1.5.0, you can test server functions directly using the testServer function, which might be what you are looking for.
Here is a reprex of how you can achieve that:
library(shiny)
library(magrittr)
library(dplyr)
mod_import_server <- function(id){
moduleServer( id, function(input, output, session){
data <- reactive({
mtcars %>%
slice(n = 1:input$n_rows)
})
return(data)
})
}
shiny::testServer(mod_import_server, {
for (i in 1:10){
session$setInputs(n_rows = i)
testthat::expect_equal(
data(),
slice(mtcars, n = 1:i)
)
}
})
Here, you can test that your reactive() behave the way you're expecting.
That's not perfect but a good start :)
It's hard to find a way to test for the behaviour of the second module though, as it depends on a reactive() passed as a value.
Colin
In a Shiny app, I am trying to have an eventReactive triggered either with an actionbutton OR when the session opens.
I've tryed the below code using session$clientData but it doesn't work.
Also tried to play with toggleModal from shinyBS but still no chance. Any help much appreciated. Thanks!
library(shiny)
ui <- fluidPage(
actionButton("go", "Go"),
numericInput("n", "n", 50),
plotOutput("plot")
)
server <- function(session, input, output) {
randomVals <- eventReactive({input$go
session$clientData}, {
runif(input$n)
})
output$plot <- renderPlot({
hist(randomVals())
})
}
shinyApp(ui, server)
Actually figured this out. On my question above, I tried to simplified my code, and doing so actually fixed the issue...
randomVals <- eventReactive({input$go
session$clientData}
works as expected (i.e. you get a chart when opening the session even without clicking on go), while
randomVals <- eventReactive({session$clientData
input$go}
doesn't work (i.e you need to click on go to get your first chart
So I guess the order in the event {} matters, which I didn't know
I'm developing a rather large shiny app and used the methods from this talk on shinydevcon to modularize it.
Now I was trying to use the package shinyFiles to get a "Save as" type dialog:
library(shiny)
library(shinyFiles)
# Dummy module for MCVE
saveModule <- function(input, output, session) {
# Taken straight from shinyFilesExample()
volumes <- getVolumes()
shinyFileSave(input, NS("test")("save"), roots = volumes, session = session)
}
server <- function(input, output, session) {
callModule(saveModule, "test")
}
ui <- shinyUI(fluidPage(
# Taken straight from shinyFilesExample()
shinySaveButton(NS("test")("save"), 'Save file', 'Save file as...', filetype=list(text='txt', picture=c('jpeg', 'jpg'))))
)
runGadget(app = ui, server = server, viewer = dialogViewer(title = "test"))
This doesn't work correctly when trying to navigate folders or switch roots.
When pulling the code out of the callModule, it does work, but as I said, the actual use case has a rather large app and I don't want to clutter the app.R with code from a single module.
Is there any way to get shinyFiles to work correctly in a callModule context? Or some other viable work-around?
For reference, here is a working server:
server <- function(input, output, session) {
volumes <- getVolumes()
shinyFileSave(input, NS("test")("save"), roots = volumes, session = session)
}
with ui and runGadget as above.