Properly display all outputs in a Shiny dashboard after bookmarking/restoration - r

When you do restore a Shiny session using the native Bookmark functionality in a dashboard with multiple tabItems, you fall on the one which was active at the moment of the bookmarking (legit). However, outputs (tab, plots, ...) from other tabItems() are not refreshed/recalculated until you actually click on them and be displayed in the browser. Is there a way to also during the restoration process recalculate those undisplayed outputs?
One possible non-elegant work around would be to have at the end of the onRestored() a bunch of updateTabItems() for each existing tabItems we want to display properly.

For those that could be interested, a general answer to this general question and as #starja suggested, is to use suspendWhenHidden = TRUE.
The idea is quite simple: by default each output object of a Shiny application is not run until it is requested to be displayed within the browser (suspendWhenHidden = TRUE). To change that behavior, simply add the following line of code in your server (not sure there is a specific place):
outputOptions(output, "my_output_name", suspendWhenHidden = FALSE)
Therefore, after a session restauration (bookmarking), even if you land on tab "B", every output of tab "A" with suspendWhenHidden = FALSE will be executed, therefore leading your reactive expressions depending on those outputs to be "refreshed" and correctly ran/displayed within tab "B".

Related

How to suppress R Shiny's numericInput instant update?

I have the following issue with the behaviour of R shiny's numeric input behaviour. Consider the following snippet:
ui <- basicPage(
numericInput("example","Example",value=0),
verbatimTextOutput("changelog")
)
server <- function(input,output){
output$changelog <- renderPrint(input$example)
}
shinyApp(ui,server)
Suppose that I want to update the example input field to 12345. My issue is that the default event listener would react to every keystroke. Thus the input field would be set to 1, 12,123 and 1234 before I finally get the desired value of 12345. Each numeric input set would be followed by an expensive computation - so this is a very undesirable behaviour.
What I am after is modifying this behaviour so that the event listener only reacts to the numeric input when the user hits enter or leaves the input field. I have currently two approaches to this:
Use a reactiveValue with an update actionButton so that the input is updated only when the user clicks update. I find this an inelegant solution and only shirks the original problem without solving it.
Modify the local shiny.js file directly and remove the keyup.textInputBinding event. That creates another issue with running the shiny app on other computers and it would make this modified behaviour uniform for all numericInput.
I'm wondering if anyone has a solution/suggestion to this? Preferably something that does not involve changing the local shiny.js file. I'm guessing a solution would involve using shinyjs::runjs to manually unsubscribe the keyup.textInputBinding event - but I don't know enough JavaScript to execute it.
You can slow frequent invalidation down with debounce or throttle. In your case, my first guess would be debounce: Debouncing means that invalidation is held off for millis milliseconds.
The reactive expression will only be validated until that time window has passed without a subsequent invalidation which may have an effect like this: ooo-oo-oo---- => -----------o-
In your case:
library(shiny)
ui <- basicPage(
numericInput("example","Example",value=0),
verbatimTextOutput("changelogImmediate"),
verbatimTextOutput("changelog")
)
server <- function(input,output){
exampleInput <- reactive(input$example) %>% debounce(1000)
# debouncedExampleInput <- exampleInput
output$changelogImmediate <- renderPrint(input$example)
output$changelog <- renderPrint(exampleInput())
}
shinyApp(ui,server)

Fix Shiny Input

I'm making a shiny app that takes a numericInput(size,...) and displays a data frame of random numbers with input$size rows, and then saves it as a csv. I'm looking for some way to prevent the user of the app to change the inputed number once they have provided it. For example, if the user sees the data frame and thinks "Oh, I don't like these numbers", I want to make sure they cannot just keep entering numbers until they get a result they want (without closing and reopening the app). Is there someway to fix the first input as it is given? Thank you so much!
You can use a combination of reactiveValue and observeEvent with the parameter once = TRUE
This will allow the reactiveValue to only be set once. The user can then change the input but it will have no effect on the rest of the app
size <- reactiveVal()
observeEvent(input$size,{
size(input$size)
},
once = TRUE)
You might have to look into the parameters ignoreInit and ignoreNULL depending on how you initate your numericInput.

shiny app how to update part of a uiOutput block instead of the whole block

Hello I have a shiny app that should read a list of configurations from a database and prompt the user with the list of configurations.
The list depends on some GET parameters, according to them the list can be different.
I tried two approaches:
in ui.R I put only one big uiOutput element and inside it's implementation (in server.R) I will do an lapply and foreach configuration I will output a fluidRow with some elements in it.
ui.R:
uiOutput("serversList")
server.R:
output$serversList <- renderUI({
lapply( get.servers()$server, function(servName) {...
in ui.R I put an lapply based on the list that comes from the database, and for each configuration I will output a fluidRow with new output objects. In server.R I put another lapply based on the same list that comes from the database, and for each configuration i will define the implementation of each output object dynamically defined in ui.R
ui.R
fluidRow(
box(width=12,
lapply(get.servers()$server, function(serv) {...
list(
uiOutput(paste0('conf', serv)),...
server.R
lapply(get.servers()$server, function(servName) {
output[[paste0('conf', servName)]] <- renderUI({...
solution 1 works, but every time that I change some configuration the whole list of configurations is refreshed, and the values set by the user were lost.
I need a way to control what object should be updated when.
solution 2 doesn't work if I fetch data from a database (in server.R).
If instead I fetch the data from a plain text file in global.R, solution 2 works and I can control what object should be updated when, because each output object is refreshed when the input objects used inside it changes.
Is there any solution 3? Or 1/2 can be fixed?
I found the solution.
I can fix solution 1, and inside renderUI for serversList
I can define other renderUIs, return a fluidRow with uiOutput connected with the renderUIs.
Long story made short: nest solution 2 inside solution 1.

Q: K. Rohde's shiny leaflet popup: how to ensure popups are properly displayed regardless of initial tabPanel?

In implementing an advanced shiny popup solution from K. Rohde, I've run into a problem using a navbarPage and tabPanels. Per the linked solution, the following code is in the appropriate tabPanel:
tabPanel("Multiple Locations",
uiOutput("script"),
tags$div(id="garbage"),
...rest of UI...
)
If Multiple Locations is the only tabPanel, or if navbarPage(selected="Multiple Locations") is set, everything works wonderfully (I have implemented nearly identically to the example in the linked solution). But if navbarPage(selected="someOtherPanel") is set, and the user subsequently navigates to the Multiple Locations tabPanel, the popups show up empty.
I've tried moving the uiOutput("script") and tags$div(id="garbage") lines into the other tabPanel (the one that's active on startup), I've tried moving it right under the navbarPage (before any tabPanel elements), and I've tried duplicating it in those locations as well, to no avail. Moving it right under the navbarMenu() appears to insert a div into the menu itself. Here's the setup that works:
ui<-navbarPage(id="page", selected="Multiple Locations",
tags$head(tags$link(href="myfavicon.ico", rel="icon"),
includeCSS("www/style.css"),
tags$script(type="text/javascript", src = "baranim.js")
),
navbarMenu("Explorer",
tabPanel("Single Location",
...UI elements...
),
tabPanel("Multiple Locations",
uiOutput("script"),
tags$div(id="garbage"),
...UI elements...
)
)
)
Though the app is not yet complete, I don't imagine I'll have users starting on the Multiple Locations tabPanel. But clearly, I'd still like the popups to function.
Many thanks to K. Rohde for the original solution.
First off, I really appreciate that you liked my post. This is very encouraging.
So I tried to reconstruct your situation and managed to reproduce the behavior you explained. My detective work showed, that you have another leaflet inside the Single Locations tab. Is that correct? The solution you linked was only designed for one single leaflet. It addresses the div of class leaflet-popup-pane, but if there are more than one, only the first one which is rendered, will be adressed. This explains the behavior with the tab choice in the beginning.
I have modified the script such that all available leaflet-popup-panes are addressed:
output$script <- renderUI({
tags$script(HTML('
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if(mutation.addedNodes.length > 0){
Shiny.bindAll(".leaflet-popup-content");
};
if(mutation.removedNodes.length > 0){
var popupNode = mutation.removedNodes[0].childNodes[1].childNodes[0].childNodes[0];
var garbageCan = document.getElementById("garbage");
garbageCan.appendChild(popupNode);
Shiny.unbindAll("#garbage");
garbageCan.innerHTML = "";
};
});
});
$(".leaflet-popup-pane").each(function() {
observer.observe(this, {childList: true});
});
'))
})
This should do the trick.
Some advice on placement: The garbage-div must only be placed once, to avoid duplicate Id problems. But it can be placed anywhere in the ui. If you have multiple leaflets with expanded popups, place one script output beneath each of those (or one beneath the last if there are several on the same page). Especially with tabPanels, this ensures that your leaflets have been fully rendered when the script is executed.
Please comment, if this doesn't solve your problem or if there is something I missed.

Accessing uiOutput Value On App Load

To simplify this example I've only included the necessary code to describe the issue I'm having. Should it be required, I will include a fully reproducible example, but I have a hunch that this issue can be solved through theory alone by someone with more experience using Shiny. Basically, I've programmed a Shiny app in R which looks something like this:
ui.R
plotOutput(outputId = 'heatmap1', height = "800px")
uiOutput('selectStrains')
uiOutput('selectRegions')
server.R
output$selectStrains = renderUI({
checkboxGroupInput(inputId='strains',
choices=sort(colnames(mousedata)),
selected=colnames(mousedata))
})
output$selectRegions = renderUI({
checkboxGroupInput(inputId='regions',
choices=sort(rownames(mousedata)),
selected=rownames(mousedata))
})
# more code
output$heatmap1 = renderPlot({
input$recalculate
mousedatamat = as.matrix(mousedata[isolate(input$strains), isolate(input$regions)])
heatmap.2(mousedatamat)
})
problem:
My problem is that in server.R, when the app is first loaded, input$strains and input$regions are both NULL. Therefore, mousedatamat in server.R will be a 0x0 matrix, and the heatmap will be empty on the front page of the app, which makes for a pretty poor user experience. I don't know how the ui.R and server.R files interact when an app is launched, so I'm finding it difficult to debug. To make the plot show up I either need to click a recalculate button (input$recalculate), or resize the window (but only in the horizontal dimension for some reason).
To add more mystery to the mix, I have the same heatmap figure on page 2 (I'm using a navbarPage layout), which shows up on the app load when I navigate to that tab! It is the very same code, only instead it is assigned to output$heatmap2. When I navigate back to page 1, output$heatmap1 still does not display.
I've tried placing the calls to uiOutput above plotOutput in ui.R, but the main reason I don't know how to solve this I think is because I don't know much about execution flow when an app is started. Any ideas or information on this topic?

Resources