I have a question about the Output Function in a Shiny application. Is it possible to write an output function with a variable as name to use it multiple times?
For example a short extract:
output$MainBody <- renderUI({
fluidPage(
gradientBox(
title = "Test",
)
)
})
Is it possible to use a function like this:
dt_representation <- function(x){
output$x <- renderUI({
fluidPage(
gradientBox(
title = "Test",
)
)
})
}
And call this funcion with:
dt_representation(MainBody)
Is that a possibility, or doesn't that work in Shiny?
I would strongly recommand to use modules as Pork Chop said.
But it can happen sometime I use such a little "hack" :
library(shiny)
ui <- fluidPage(
uiOutput("all_id")
)
server <- function(input, output) {
# Define function
createUI <- function(x, text) {
output[[x]] <<- renderUI({
div(text)
})
}
# Use function
createUI("id1", "Here is my first UI")
createUI("id2", "Here is my second UI")
# Combine all in one
output$all_id <- renderUI({
do.call(fluidRow, lapply(c("id1","id2"), uiOutput))
})
}
shinyApp(ui = ui, server = server)
Related
Is there any way to apply a req command to multiple output objects in a shiny app without having to repeat the req statement each time? In the example below, you will see that the req statement is repeated in both the part1 and part2 outputs.
library(shiny)
ui <- fluidPage(
textInput("commentText", "Input Text"),
textOutput(outputId = "part1"),
textOutput(outputId = "part2")
)
server <- function(input, output, session) {
output$part1 <- renderText({
req(input$commentText)
"Hello"
})
output$part2 <- renderText({
req(input$commentText)
"World!"
})
}
shinyApp(ui, server)
If possible, I would only like to have to input the req statement once (the actual use case has 6 different objects tied to the req so I'd like to avoid repetition if possible). I tried the following approach using an observer, but this fails when you type in and then delete text (when the text is deleted, 'HelloWorld!' still appears).
server <- function(input, output, session) {
observeEvent(input$commentText, {
req(input$commentText)
output$part1 <- renderText({
"Hello"
})
output$part2 <- renderText({
"World!"
})
})
}
It is not recommended to nest a render function inside an observer. Instead, you could use a renderUI and handle both texts.
req allows more than one argument. You can check several inputs in one statement.
req(..., cancelOutput = FALSE)
... Values to check for truthiness.
Attempted solution:
library(shiny)
ui <- fluidPage(
textInput("commentText", "Input Text"),
textInput("commentText2", "Input More Text"),
uiOutput(outputId = "part1and2")
)
server <- function(input, output, session) {
output$part1and2 <- renderUI({
req(input$commentText, input$commentText2)
tagList(
wellPanel("Hello"),
wellPanel("World!")
)
})
}
shinyApp(ui, server)
Here's server.r
server <- function(input, output) {
output$species <- renderUI({
selectInput("species",
label = "blah",
choices = as.list(unique(iris$Species)))
})
}
Then over in ui.r
ui <- fluidPage(
fluidRow(
uiOutput("species")
)
This works as expected, a drop down select input appears like this:
Since I have multiple features I need to create a similar filter for in my actual data frame, I tried to do the same with a function:
In server.r
outputFilters <- function(id, df) {
output$id <- renderUI({
selectInput(id,
label = "blah",
choices = as.list(unique(df$id)))
})
}
outputFilters("species", iris)
Then in ui.r same as before uiOutput("species")
However, now no drop down appears. Presumably my function is flawed. How can I use a function to generate the drop downs?
Note that you could also do without a separate function in this case, by wrapping the desired ui component in lapply, or putting the lapply within the uiOutput to create all inputs at once, below is an example for the both two cases. Hope this helps!
ibrary(shiny)
ui <- fluidPage(
uiOutput('Species'),
uiOutput('Sepal.Length'),
h2('All inputs: '),
uiOutput('my_inputs')
)
server <- function(input, output) {
# Use lapply to create multiple uiOutputs.
lapply(colnames(iris), function(x){
output[[x]] <- renderUI({
selectInput(paste0('input_',x),
label = x,
choices = as.list(unique(iris[['x']])))
})
})
# Create all dropdown's at once.
output$my_inputs <- renderUI({
lapply(colnames(iris), function(x){
selectInput(paste0('input_',x),
label = x,
choices = as.list(unique(iris)))
})
})
}
shinyApp(ui, server)
Your problem is that each UI element needs its own id in the output
outputFilters <- function(id, df) {
output[[id]] <- renderUI({
selectInput(id,
label = "blah",
choices = as.list(unique(df[[id]])))
})
}
now as long as id is a string in the function input it should generate the output element and you can refer with said id
You could then even use lapply to iterate over numerous, kind of how florian suggests.
Here is a simple demo of the problem:
library(shiny)
ui <- fluidPage(
textOutput("Text1"),
textOutput("Text2")
)
server <- function(input, output) {
for(i in 1:2) {
id <- paste0("Text", i)
output[[id]] <- renderText(paste0("This is text #", i)) # Problem!
}
}
shinyApp(ui, server)
This program produces output
This is text #2
This is text #2
rather then #1 and #2.
Evidently, Shiny stores the expressions passed to renderText() in the line marked # Problem!, and evaluates them after the for-loop is finished. The expressions depend on variable i, and its final value i = 2 is used in evaluating both expressions.
How can I produce correct output (how can I force Shiny to use different values of i in different expressions), while still using the loop? In my code the loop limits are dynamic, and I cannot replace the loop with several static calls.
Why the for-loop does not work, check the output of this example:
library(shiny)
ui <- fluidPage(
textOutput("Text1"),
textOutput("Text2")
)
server <- function(input, output) {
for(i in 1:3) {
id <- paste0("Text", i)
output[[id]] <- renderText(paste0("This is text #", i)) # Problem!
}
i=10 # we set i to 10.
}
shinyApp(ui, server)
As you can see, all renderText elements use the last (global) value for i. This is not the case in the lapply, where an argument is passed to the function, but that argument is not defined in the global environment.
So you could use lapply instead of a for-loop, like this:
library(shiny)
ui <- fluidPage(
textOutput("Text1"),
textOutput("Text2")
)
server <- function(input, output) {
lapply(1:2,function(i){
id <- paste0("Text", i)
output[[id]] <- renderText(paste0("This is text #", i)) # Problem!
})
}
shinyApp(ui, server)
Output:
If you also want the ui to be reactive, you could use renderUI and uiOutput, for example as follows:
library(shiny)
ui <- fluidPage(
numericInput("number_of_text","Number of text",min=1,max=10,value=3),
uiOutput('my_text')
)
server <- function(input, output) {
reactive_text <- reactive({
all_text <- lapply(1:input$number_of_text,function(i){
id <- paste0("Text", i)
renderText(paste0("This is text #", i)) # Problem!
})
# do.call(all_text,tagList)
})
output$my_text <- renderUI({
do.call(fluidRow, reactive_text())
})
}
shinyApp(ui, server)
Output:
Hope this helps!
I'd like to output several tables as a one uiOutput. If I put them together in a list using a loop then all outputs are equal to the last one.
Example:
library(shiny)
ui <- fluidPage(
mainPanel(
uiOutput("tables")
)
)
server <- function(input, output) {
output$tables <- renderUI({
data=array(rnorm(150),c(10,5,3))
tfc = function(m){
# x = m[1,1]
renderTable({m})
}
result=list()
for(i in 1:3)
result[[i]] = tfc(data[,,i])
return(result)
})
}
shinyApp(ui = ui, server = server)
If I remove the commented line (x = m[1,1]) I get the desired result.
I can live with this workaround but is there a reason why shiny behaves like that or is there a different way to do it?
I usually use lapply for such usecases. This way, you don't run into issues with lazy evaluation.
library(shiny)
ui <- fluidPage(
mainPanel(
uiOutput("tables")
)
)
server <- function(input, output) {
output$tables <- renderUI({
data=array(rnorm(150),c(10,5,3))
tfc = function(m){renderTable({m})}
lapply(1:3, function(i){tfc(data[,,i])})
})
}
shinyApp(ui = ui, server = server)
If you want to use a reacive table, you can use something like
tfc = function(m, output, id){
output[[id]] <- renderTable({m()})
tableOutput(id)
}
instead.
To get around this, you can force evaluation of function arguments:
tfc = function(m) {
force(m)
renderTable(m)
}
or
create a local scope for each loop iteration:
for (i in 1:3) {
local({
i <- i
result[[i]] <<- tfc(data[,,i])
})
}
lapply works as well, but only for R versions 3.2 and above: https://cran.r-project.org/bin/windows/base/old/3.2.0/NEWS.R-3.2.0.html
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.