Closed DivadNojnarg closed 3 years ago
I edited the code above to consider hidden outputs that could happen in a tab
based layout. I explored the Shiny.shinyapp.$initialInput
object to recover the id of the hidden plot(s) and remove it/them from the Object.keys(Shiny.shinyapp.$bindings).length
count:
let hiddenOutputsIds = Object.keys(Shiny.shinyapp.$initialInput)
.filter(function(q){return /_hidden/.test(q)})
.filter(function(t) {return Shiny.shinyapp.$initialInput[t] === true})
.map(function(x) {return x.split('_output_')[1].split('_hidden')[0]})
.length;
Then an example:
library(shiny)
library(waiter)
ui <- navbarPage(
"App Title",
tabPanel(
"Tab 1",
use_waiter(),
waiter_show_on_load(),
tags$script(
HTML(
"$(function() {
let counter = 0, hasWaiter;
const hideWaiterCallback = function() {
let hiddenOutputsIds = Object.keys(Shiny.shinyapp.$initialInput)
.filter(function(q){return /_hidden/.test(q)})
.filter(function(t) {return Shiny.shinyapp.$initialInput[t] === true})
.map(function(x) {return x.split('_output_')[1].split('_hidden')[0]})
.length;
counter +=1;
console.log({counter, hiddenOutputsIds});
hasWaiter = ($('.waiter-overlay').length > 0);
if (hasWaiter) {
if (counter === Object.keys(Shiny.shinyapp.$bindings).length - hiddenOutputsIds) {
window.hide_waiter(null);
$(document).trigger('remove_waiter_on_start');
}
}
}
$(document).on('shiny:value', hideWaiterCallback);
$(document).on('remove_waiter_on_start', function() {
$(document).off('shiny:value', hideWaiterCallback);
});
});
"
)
),
sliderInput("obs", "Number of observations:",
min = 0, max = 1000, value = 500
),
plotOutput("distPlot")
),
tabPanel(
"tab 2",
radioButtons("dist", "Distribution type:",
c("Normal" = "norm",
"Uniform" = "unif",
"Log-normal" = "lnorm",
"Exponential" = "exp")),
plotOutput("distPlot2")
)
)
server <- function(input, output, session) {
output$distPlot2 <- renderPlot({
Sys.sleep(4)
dist <- switch(input$dist,
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
hist(dist(500))
})
output$distPlot <- renderPlot({
Sys.sleep(2)
hist(rnorm(input$obs))
})
}
shinyApp(ui, server)
Thanks David!
I was wondering if something simpler, that you suggested earlier, would work; observing the idle
event.
library(shiny)
library(waiter)
script <- "
window.loaded = false;
$(document).on('shiny:idle', function(e) {
if(!window.loaded)
hide_waiter(null);
window.loaded = true;
})"
ui <- fluidPage(
tags$head(
tags$script(script)
),
use_waiter(),
waiter_show_on_load(),
actionButton("draw", "render stuff"),
uiOutput("x")
)
server <- function(input, output){
Sys.sleep(3)
output$x <- renderUI({
input$draw
h1("Stuff")
})
}
shinyApp(ui, server)
Umm, this is weird. I tried this yesterday and it failed because Shiny went idle twice between 2 rendering, which hide the waiter while the plot was still loading, for some reason.
Today looks normal.
The problem I mentioned about observeEvent
running after startup:
ui <- fluidPage(
use_waiter(),
waiter_show_on_load(),
tags$script(
HTML(
"window.loaded = false;
$(document).on('shiny:message', function(e) {
if(!window.loaded)
console.log(e.message);
});
$(document).on('shiny:idle', function(e) {
if(!window.loaded)
hide_waiter(null);
window.loaded = true;
});
$(document).trigger('idle');
"
)
),
sliderInput("obs", "Number of observations:",
min = 0, max = 1000, value = 500
),
plotOutput("distPlot"),
radioButtons("dist", "Distribution type:",
c("Normal" = "norm",
"Uniform" = "unif",
"Log-normal" = "lnorm",
"Exponential" = "exp")),
plotOutput("distPlot2")
)
server <- function(input, output, session){
observeEvent(input$obs, {
updateRadioButtons(session, "dist", selected = "unif")
})
output$distPlot2 <- renderPlot({
Sys.sleep(2)
dist <- switch(input$dist,
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
hist(dist(500))
})
output$distPlot <- renderPlot({
Sys.sleep(4)
hist(rnorm(input$obs))
})
}
shinyApp(ui, server)
As discussed here: Here are my thoughts
You can create a counter that increases each time an output is rendered and compare it to the value of Object.keys(Shiny.shinyapp.$bindings).length. This gives you the number of output to render, when the app starts. Don't try Object.keys(Shiny.shinyapp.$values).length since values are computed 1 by 1 and its initial value is not the equal to the number of outputs to render. When counter is equal to the total number of outputs, you hide the waiter:
Moreover, since we don't want this event to trigger after the app is launched (It may mess with all other events), we trigger a custom
remove_waiter_on_start
event and disable the hideWaiterCallback from the document.It seems to work but what happens, let's say, if an observeEvent runs shortly after all output are rendered, updating an input, which invalidates one of the output and triggers a rather long computation? Is it wise the hide the waiter? This is basically not possible to answer...
Example:
Let's discuss and I can PR if needed.