Generate progress bar in modal in shiny app, that closes automatically - r

I am working on a shiny app that takes a long time to do calculations, I want to have a modal progress bar that closes automatically as soon as all calculations work.
The ideal solution would have two features
Covers most of the screen and prevents the user to interact with app
Closes automatically as soon as it finishes making calulations
I found this solution in the following question:
library("shiny")
library("shinyWidgets")
ui <- fluidPage(
actionButton(inputId = "go", label = "Launch long calculation"), #, onclick = "$('#my-modal').modal().focus();"
# You can open the modal server-side, you have to put this in the ui :
tags$script("Shiny.addCustomMessageHandler('launch-modal', function(d) {$('#' + d).modal().focus();})"),
tags$script("Shiny.addCustomMessageHandler('remove-modal', function(d) {$('#' + d).modal('hide');})"),
# Code for creating a modal
tags$div(
id = "my-modal",
class="modal fade", tabindex="-1", `data-backdrop`="static", `data-keyboard`="false",
tags$div(
class="modal-dialog",
tags$div(
class = "modal-content",
tags$div(class="modal-header", tags$h4(class="modal-title", "Calculation in progress")),
tags$div(
class="modal-body",
shinyWidgets::progressBar(id = "pb", value = 0, display_pct = TRUE)
),
tags$div(class="modal-footer", tags$button(type="button", class="btn btn-default", `data-dismiss`="modal", "Dismiss"))
)
)
)
)
server <- function(input, output, session) {
value <- reactiveVal(0)
observeEvent(input$go, {
shinyWidgets::updateProgressBar(session = session, id = "pb", value = 0) # reinitialize to 0 if you run the calculation several times
session$sendCustomMessage(type = 'launch-modal', "my-modal") # launch the modal
# run calculation
for (i in 1:10) {
Sys.sleep(0.5)
newValue <- value() + 1
value(newValue)
shinyWidgets::updateProgressBar(session = session, id = "pb", value = 100/10*i)
}
Sys.sleep(0.5)
# session$sendCustomMessage(type = 'remove-modal', "my-modal") # hide the modal programmatically
})
}
shinyApp(ui = ui, server = server)
This solves issue 1, but I have to click on dismiss to see the results

The original progressbar provided in shiny is exactly what you need.
But I use css to make the progessbar display in the middle in the screen.
You can find the detail of using progress bar in shiny here.
library("shiny")
ui <- fluidPage(
actionButton(inputId = "go", label = "Launch long calculation"), #, onclick = "$('#my-modal').modal().focus();"
# css to center the progress bar
tags$head(
tags$style(
HTML(".shiny-notification {
height: 100px;
width: 800px;
position:fixed;
top: calc(50% - 50px);
left: calc(50% - 400px);
font-size: 250%;
text-align: center;
}
"
)
)
)
)
server <- function(input, output, session) {
value <- reactiveVal(0)
observeEvent(input$go, {
withProgress(message = 'Calculation in progress', value = 0,detail="0%", {
# run calculation
for (i in 1:10) {
Sys.sleep(0.5)
newValue <- value() + 1
value(newValue)
incProgress(1/10,detail = paste0(i*10,"%"))
}
Sys.sleep(0.5)
})
})
}
shinyApp(ui = ui, server = server)

Not a whole answer, just answering the additional css requests. You could change the css to, which will make the panel fill up the whole page.
.shiny-notification {
height: 100%;
width: 100%;
top: 0;
left: 0;
position:fixed;
font-size: 250%;
text-align: center;
background-color: rgba(0, 0, 0, 0.7);
color: white;
}

Related

Move previous next buttons in R's slickR carousel

I can successfully move the "next" button for slickR's carousel. However, when I use the similar method to move the "previous" button it does not work. The action and the mouseover no longer work. Why is this? How can I move the "prev" button and maintain full functionality?
The documentation refers to an element in settings called, appendArrows. But it is not clear to me how to use this.
appendArrows character, Change where the navigation arrows are attached (Selector, htmlString, Array, Element, jQuery object), Default: $(element)
Here is where the fully functional moved buttons should appear:
library(shiny)
library(slickR)
# Test #########################################################################
chart_names_list <- c( "http://placehold.it/900x500/39CCCC/ffffff&text=Slide+1",
"http://placehold.it/900x500/39CCCC/ffffff&text=Slide+2",
"http://placehold.it/900x500/39CCCC/ffffff&text=Slide+3")
num_slides <- 2
ui <- fluidPage(
tags$head(
tags$style(HTML("
.slick-next {
right: 163px;
top: 20px;
}
.slick-prev {
left: 670px;
top: 20px;
}
.slick-slide {
margin-top: 30px;
}
")
)
),
slickROutput("slick_output")
)
server <- function(input, output, session) {
output$slick_output <- renderSlickR({
slickR(obj = chart_names_list, height = 300, width = "100%") +
settings(dots = TRUE)
})
}
shinyApp(ui, server)
appendArrows parameter is used to tell in which div class the arrows should be displayed.
This shows the principle, but still needs some extra css to get exactly the result you expect :
library(shiny)
library(slickR)
# Test #########################################################################
chart_names_list <- c( "http://placehold.it/900x500/39CCCC/ffffff&text=Slide+1",
"http://placehold.it/900x500/39CCCC/ffffff&text=Slide+2",
"http://placehold.it/900x500/39CCCC/ffffff&text=Slide+3")
num_slides <- 2
ui <- fluidPage(
tags$head(
tags$style(HTML("
.arrows {
height: 30px;}"))
),
fluidRow(
column(6,),
column(2,
tags$div(class="arrows"
)),column(4)),
slickROutput("slick_output")
)
server <- function(input, output, session) {
output$slick_output <- renderSlickR({
slickR(obj = chart_names_list, height = 300, width = "100%") +
settings(dots = TRUE, appendArrows = '.arrows')
})
}
shinyApp(ui, server)
Taking #Waldi's valuable suggestions and adding some css leads to a complete answer below.
library("shiny")
library("slickR")
# Test #########################################################################
chart_names_list <- c( "http://placehold.it/900x500/39CCCC/ffffff&text=Slide+1",
"http://placehold.it/900x500/39CCCC/ffffff&text=Slide+2",
"http://placehold.it/900x500/39CCCC/ffffff&text=Slide+3")
num_slides <- 3
ui <- fluidPage(
tags$head(
tags$style(HTML("
.arrows {
height: 30px;
}
.slick-prev {
left: 230px; # moves right
}
.slick-next {
left: 250px; # moves right
}
"))
),
fluidRow(
column(6,),
column(2,
tags$div(class="arrows"
)),
column(4)),
slickROutput("slick_output")
)
server <- function(input, output, session) {
output$slick_output <- renderSlickR({
slickR(obj = chart_names_list, height = 300, width = "100%") +
settings(dots = TRUE, appendArrows = '.arrows')
})
}
shinyApp(ui, server)
As this is the original question regarding the positioning of the arrow buttons, I guess it's worth mentioning, that #ixodid realized here, that #Waldi's column-approach is no longer working when the browser window is resized.
The following is a workaround regarding this:
library("shiny")
library("slickR")
chart_names_list <- c( "http://placehold.it/900x500/39CCCC/ffffff&text=Slide+1",
"http://placehold.it/900x500/39CCCC/ffffff&text=Slide+2",
"http://placehold.it/900x500/39CCCC/ffffff&text=Slide+3")
num_slides <- 3
ui <- fluidPage(
tags$head(
tags$style(HTML("
.arrows {
height: 20px;
}
.slick-prev {
left: calc(100% - 60px);
}
.slick-next {
left: calc(100% - 35px);
}
.slick-slide img {
width: 100% !important;
}
"))
),
fluidRow(
column(12, tags$div(class="arrows"))
),
slickROutput("slick_output")
)
server <- function(input, output, session) {
output$slick_output <- renderSlickR({
slickR(obj = chart_names_list, height = 300) +
settings(dots = TRUE, appendArrows = '.arrows')
})
}
shinyApp(ui, server)

Image not rendering inside Canvas using Shiny renderImage

I'm trying to use Fabric.js interactively in a Shiny app. Shiny should render the image, then the user will modify it via Fabric. Then, Shiny will read that image back in to the server and further process it. However, I can't get past the first step at the moment!
In the code below, the plot/png displays below the canvas, but not inside of it. Is how I reference the image id in the javascript wrong?
Maybe it is because the renderImage places the image inside a div?
Here's the code:
library(shiny)
js <- "
$(document).ready(function () {
var canvas = new fabric.Canvas('drawarea');
var imgElement = document.getElementById('myimage');
var imgInstance = new fabric.Image(imgElement, {
left: 100,
top: 100,
angle: 0,
opacity: 0.75,
width:300,
height:300
});
canvas.add(imgInstance);
});
"
ui <- fluidPage(
tags$head(tags$script(HTML(js))),
tags$head(tags$script(src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js", type="text/javascript")),
tags$head(
tags$style(HTML("
canvas {
border: 1px solid #999;
}
myimage{display:none;}
"))
),
titlePanel("Fabric Demo"),
sidebarLayout(
sidebarPanel(
h4("Side")
),
mainPanel(
HTML('
<canvas width="800" height="800" id="drawarea" style="border: 1px solid red;float: right"> </canvas>
'),
plotOutput("div.for.image")
)
)
)
server <- function(input, output, session) {
output$div.for.image <- renderImage({
outfile <- tempfile(fileext='.png')
# Generate a png
png(outfile, width=400, height=400)
hist(rnorm(20))
dev.off()
# Return a list
list(src = outfile,
alt = "This is alternate text",
id = "myimage")
}, deleteFile = TRUE)
}
shinyApp(ui=ui,server=server)
You can use the shiny:value JavaScript event, which is triggered when an output is rendered.
library(shiny)
js <- "
$(document).on('shiny:value', function(evt) {
if(evt.name === 'divForImage') {
setTimeout(function(){
var canvas = new fabric.Canvas('drawarea');
var imgElement = document.getElementById('myimage');
var imgInstance = new fabric.Image(imgElement, {
left: 100,
top: 100,
angle: 0,
opacity: 0.75,
width: 300,
height: 300
});
canvas.add(imgInstance);
}, 0);
}
});
"
# ui #########
ui <- fluidPage(
tags$head(
tags$script(
src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"
),
tags$script(HTML(js)),
tags$style(HTML(
"canvas {border: 1px solid #999;}
#myimage{display:none;}"))
),
titlePanel("Fabric Demo"),
sidebarLayout(
sidebarPanel(
h4("Side")
),
mainPanel(
tags$canvas(
width="800", height="800", id="drawarea", style="border: 1px solid red; float: right;"
),
imageOutput("divForImage")
)
)
)
## server #######
server <- function(input, output, session) {
output[["divForImage"]] <- renderImage({
outfile <- tempfile(fileext='.png')
# Generate a png
png(outfile, width=400, height=400)
hist(rnorm(20))
dev.off()
# Return a list
list(src = outfile,
alt = "This is alternate text",
id = "myimage")
}, deleteFile = TRUE)
}
shinyApp(ui=ui, server=server)
Note: don't use periods in ids, this can lead to some issues.
You have to trigger the JavaScript code after the image has been inserted by Shiny. Right now you a re firing the code as soon as the document is ready.
I would use shinyjs for this task and use the runjs and delay function, to wait until the list with the image is returned and visible in the HTML.
If you want to hide the image rendered by Shiny you need an # in the css (like #myimage)
Full Code:
library(shiny)
library(shinyjs)
js <- "
$(document).ready(function () {
var canvas = new fabric.Canvas('drawarea');
var imgElement = document.getElementById('myimage');
var imgInstance = new fabric.Image(imgElement, {
left: 100,
top: 100,
angle: 0,
opacity: 0.75,
width:300,
height:300
});
canvas.add(imgInstance);
});
"
# ui #########
ui <- fluidPage(
useShinyjs(),
tags$head(tags$script(src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js", type="text/javascript")),
tags$head(
tags$style(HTML("canvas {border: 1px solid #999;}
#myimage{display:none;}"))
),
titlePanel("Fabric Demo"),
sidebarLayout(
sidebarPanel(
h4("Side")
),
mainPanel(
HTML('<canvas width="800" height="800" id="drawarea" style="border: 1px solid red;float: right"></canvas>'),
plotOutput("div.for.image")
)
)
)
## server #######
server <- function(input, output, session) {
output$div.for.image <- renderImage({
outfile <- tempfile(fileext='.png')
# Generate a png
png(outfile, width=400, height=400)
hist(rnorm(20))
dev.off()
delay(200, runjs(HTML(js)))
# Return a list
list(src = outfile,
alt = "This is alternate text",
id = "myimage")
}, deleteFile = TRUE)
}
shinyApp(ui=ui,server=server)

observers fire on render of dynamic UI when they should not

The problem I face is that observers linked to dynamically rendered elements seem to fire on render, while this is not how I want it to be.
The reason this is a problem, is that the color buttons I'm making are linked to a plot that takes several seconds to render (plotly widget)
I added ignoreInit = T the observers that are created, but they still fire on rendering, unlike normal observers linked to a button build directly in the UI
How do I stop the observers linked to the dynamically rendered colourInput from firing when the element is rendered?
In the dummy app below the following series of events is recreated in simplified form:
A model spits out a number (simulated by test button in demo app)
Based on this number, a number of colourInput buttons are made
A same number of observeEvents are made for each.
Not in the dummy app: When the user chooses to change a color, the corresponding group in plots is recolored accordingly
The test app contains a working static colourInput, and a dynamic part that demonstrates the problem scenario.
Test app:
library(shiny)
library("colourpicker")
THECOLORS <- c('#383838', '#5b195b','#1A237E', '#000080', '#224D17', '#cccc00', '#b37400', '#990000',
'#505050', '#a02ca0', '#000099', '#2645e0', '#099441', '#e5e500', '#cc8400', '#cc0000',
'#737373', '#e53fe5', '#0000FF', '#4479e1', '#60A830', '#ffff00','#e69500', '#ff0000',
'#b2b2b2', '#eb6ceb', '#6666ff', '#d0a3ff', '#9FDA40', '#ffff7f', '#ffa500', '#ff4c4c')
ui <- fluidPage(
h1("WELCOME TO THE TEST APP", style = 'text-align: center; font-weight:bold' ),
br(),
h3("STATIC PART: doesn't fire on startup, great!", style = 'font-weight:bold'),
div(colourpicker::colourInput(inputId = 'StaticColor', label = NULL, palette = "limited", allowedCols = THECOLORS, value = THECOLORS[14], showColour = "background", returnName = TRUE),
style = " height: 30px; width: 30px; border-radius: 6px; border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px"),
br(),
h3("Dynamic part: fires on render, NOT great!", style = 'font-weight:bold'),
actionButton(inputId = 'Tester', label = 'Click me'),
br(),
uiOutput('colorbutton')
)
server <- function(input, output, session) {
values <- reactiveValues()
values$mycolors <- THECOLORS
observeEvent(input$Tester, { values$NrofButtons <- sample(1:10, 1) })
observeEvent(values$NrofButtons, {
COLElement <- function(idx){sprintf("COL_button-%s-%d",values$NrofButtons,idx)}
output$colorbutton <- renderUI({
lapply(1:values$NrofButtons, function(x) {
div(colourpicker::colourInput(inputId = COLElement(x), label = NULL, palette = "limited", allowedCols = values$mycolors, value = values$mycolors[x], showColour = "background", returnName = TRUE),
style = " height: 30px; width: 30px; border-radius: 6px; border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px") })
})
lapply(1:values$NrofButtons, function(x) { observeEvent(input[[COLElement(x)]], { print(input[[COLElement(x)]] )}, ignoreInit = T) }) # make observer for each button
})
observeEvent(input[['StaticColor']], { print(input[['StaticColor']] )}, ignoreInit = T)
}
shinyApp(ui,server)
Renders should always be by themselves and be data driven, not event driven -- so I've made the render require the number of colors to be defined before rendering. Of course the number of colors aren't defined until the observeEvent is fired by clicking the button.
Overall there is still the issue that every time the button is clicked more observers are created for the same ID, working on a way to destroy these automatically on a subsequent click of the tester button.
The key addition was a ignoreInit = TRUE in your observeEvent(input$Tester, {...}) observer.
library(shiny)
library("colourpicker")
THECOLORS <- c('#383838', '#5b195b','#1A237E', '#000080', '#224D17', '#cccc00', '#b37400', '#990000',
'#505050', '#a02ca0', '#000099', '#2645e0', '#099441', '#e5e500', '#cc8400', '#cc0000',
'#737373', '#e53fe5', '#0000FF', '#4479e1', '#60A830', '#ffff00','#e69500', '#ff0000',
'#b2b2b2', '#eb6ceb', '#6666ff', '#d0a3ff', '#9FDA40', '#ffff7f', '#ffa500', '#ff4c4c')
ui <- fluidPage(
h1("WELCOME TO THE TEST APP", style = 'text-align: center; font-weight:bold' ),
br(),
h3("STATIC PART: doesn't fire on startup, great!", style = 'font-weight:bold'),
div(colourpicker::colourInput(inputId = 'StaticColor', label = NULL, palette = "limited", allowedCols = THECOLORS, value = THECOLORS[14], showColour = "background", returnName = TRUE),
style = " height: 30px; width: 30px; border-radius: 6px; border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px"),
br(),
h3("Dynamic part: fires on render, NOT great!", style = 'font-weight:bold'),
actionButton(inputId = 'Tester', label = 'Click me'),
br(),
uiOutput('colorbutton')
)
COLElement <- function(idx) sprintf("COL_button-%d", idx)
server <- function(input, output, session) {
values <- reactiveValues(previous_max = 1)
observeEvent(input$Tester, {
values$NrofButtons <- sample(1:10, 1)
# reset counters for all observers
for (i in seq(values$NrofButtons)) {
values[[sprintf("observer%d_renders", i)]] <- 0L
}
# only initialize incremental observers
lapply(values$previous_max:values$NrofButtons, function(x) {
observeEvent(input[[COLElement(x)]], {
# only execute the second time, since the `ignoreInit` isn't obeyed
if (values[[sprintf("observer%d_renders", x)]] > 0) {
print(input[[COLElement(x)]] )
} else {
values[[sprintf("observer%d_renders", x)]] <- 1L
}
}, ignoreInit = TRUE)
}) # make observer for each button
# record the max
values$previous_max <- max(values$previous_max, max(values$NrofButtons))
}, ignoreInit = TRUE)
output$colorbutton <- renderUI({
req(length(values$NrofButtons) > 0)
lapply(1:values$NrofButtons, function(x) {
div(colourpicker::colourInput(
inputId = COLElement(x)
, label = NULL
, palette = "limited"
, allowedCols = THECOLORS
, value = THECOLORS[x]
, showColour = "background"
, returnName = TRUE
)
, style = " height: 30px; width: 30px; border-radius: 6px; border-width: 2px; text-align:center; padding: 0px; display:block; margin-bottom: 10px"
)
})
})
observeEvent(input$StaticColor, {
print(input$StaticColor )
}, ignoreInit = TRUE)
}
shinyApp(ui,server)

Multiple notifications in different places shiny app

I made a simple example to show my problem, which is:
how to set notifications at different places independently of each others (let's say I want each notification to be displayed at the top right and left, respectively)
```
library(shiny)
ui <- fluidPage(
# tags$head(
# tags$style(
# HTML(".shiny-notification {
# position: fixed;
# top: 800px;
# left: 40px;
# width: 15em;
# opacity: 1;
# }
# "
# )
# )
# )
actionButton("showme", "Show Notification:")
)
server <- function(input, output, session) {
observe({
showNotification(
id = "welcome_notif",
"Blablablablabla .... blablablabla.",
duration = 20,
closeButton = TRUE,
type = "message")
})
observeEvent(input$showme,{
showNotification(
id = "showme_notif",
"Hihihi", # put text in notification
duration = 30,
closeButton = TRUE,
type = "message")
})
}
shinyApp(ui = ui, server = server)
```
I saw that there is a special CSS for notification in shiny code (https://github.com/rstudio/shiny/blob/master/inst/www/shared/shiny.css).
If I change the CSS class (as shown in commented code), I only manage to change the place where all notifications will be displayed (but not independently).
EDIT:
I tried to use addClass with shinyjs:
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
inlineCSS(list(.customclass = "position: fixed; top: 200px;")),
actionButton("showme", "Show Notification:")
)
server <- function(input, output, session) {
observe({
showNotification(
id = "welcome_notif",
"Blablablablabla .... blablablabla.",
duration = 20,
closeButton = TRUE,
type = "message")
})
observeEvent(input$showme,{
showNotification(
id = "showme_notif",
"Hihihi", # put text in notification
duration = 30,
closeButton = TRUE,
type = "message")
})
observe({
addClass(id = "showme_notif", class = "customclass")
})
}
shinyApp(ui = ui, server = server)
as suggested by Florian (see below) but it seems that I can only handle elements generated in UI and not in server like notifications.
For example this works:
if (interactive()) {
shinyApp(
ui = fluidPage(
useShinyjs(),
inlineCSS(list(.customclass = "position: fixed; top: 200px;")),
p(id = "element", "Watch what happens to me")
),
server = function(input, output) {
observe({
addClass(id = "element", class = "customclass")
})
}
)
}
I am able to modify the CSS of the element, since the notification gets the id: shiny-notifaction-xxx where xxx is the name of your notification. But all notifications are put together in another container, and I am unable to modify the CSS so that it does what you want.
library(shiny)
ui <- fluidPage(
tags$style("#shiny-notification-showme_notif {margin:20px;}"),
actionButton("showme", "Show Notification:")
)
server <- function(input, output, session) {
observe({
showNotification(
id = "welcome_notif",
"Blablablablabla .... blablablabla.",
duration = 200,
closeButton = TRUE,
type = "message")
})
observeEvent(input$showme,{
showNotification(
id = "showme_notif",
"Hihihi", # put text in notification
duration = 300,
closeButton = TRUE,
type = "message")
})
}
shinyApp(ui = ui, server = server)
So according to Florian, one answer could be:
library(shiny)
ui <- fluidPage(
tags$style("#shiny-notification-showme_notif {position: fixed; top: 800px; left: 40px; width: 15em; opacity: 1;}"),
tags$style("#shiny-notification-welcome_notif {position: fixed; top: 800px; right: 40px; width: 15em; opacity: 1;}"),
actionButton("showme", "Show Notification:")
)
server <- function(input, output, session) {
observe({
showNotification(
id = "welcome_notif",
"Blablablablabla .... blablablabla.",
duration = 200,
closeButton = TRUE,
type = "message")
})
observeEvent(input$showme,{
showNotification(
id = "showme_notif",
"Hihihi", # put text in notification
duration = 300,
closeButton = TRUE,
type = "message")
})
}
shinyApp(ui = ui, server = server)
and can be modified, as required.

Adjust size of Shiny progress bar and center it

I'm working with a Shiny app where I need to calculate processes and while the calc progress is executing, I'm using a progressBar to show the process.
The problem is that the progress bar is too small, and I don't like the way is shown.
So, I was thinking that maybe there's a way to implement a progress bar using a Shiny modal (there's a function called modalDialog).
My idea is that when the user runs the calc, a modal will be opened showing a progressBar.
This is the progress code:
withProgress(message = 'Runing GSVA', value = 0, {
incProgress(1, detail = "This may take a while...")
functionToGenerate()
})
Any idea?
I would suggest customizing the CSS class of the notification:
If you inspect the element of the notifier you see that it has the class "shiny-notification". So you can overwrite some properties of that class with tags$style(). In the example below (for the template: see ?withProgress) i decided to adjust height+width to make it bigger and top+left to center it.
ui <- fluidPage(
tags$head(
tags$style(
HTML(".shiny-notification {
height: 100px;
width: 800px;
position:fixed;
top: calc(50% - 50px);;
left: calc(50% - 400px);;
}
"
)
)
),
plotOutput("plot")
)
server <- function(input, output) {
output$plot <- renderPlot({
withProgress(message = 'Calculation in progress',
detail = 'This may take a while...', value = 0, {
for (i in 1:15) {
incProgress(1/15)
Sys.sleep(0.25)
}
})
plot(cars)
})
}
runApp(shinyApp(ui, server), launch.browser = TRUE)
I wrote a progress bar function in the package shinyWidgets, you can put it in a modal, but it's tricky to use with shiny::showModal, so you can create your own modal manually like the below. It's more code to write but it works fine.
library("shiny")
library("shinyWidgets")
ui <- fluidPage(
actionButton(inputId = "go", label = "Launch long calculation"), #, onclick = "$('#my-modal').modal().focus();"
# You can open the modal server-side, you have to put this in the ui :
tags$script("Shiny.addCustomMessageHandler('launch-modal', function(d) {$('#' + d).modal().focus();})"),
tags$script("Shiny.addCustomMessageHandler('remove-modal', function(d) {$('#' + d).modal('hide');})"),
# Code for creating a modal
tags$div(
id = "my-modal",
class="modal fade", tabindex="-1", `data-backdrop`="static", `data-keyboard`="false",
tags$div(
class="modal-dialog",
tags$div(
class = "modal-content",
tags$div(class="modal-header", tags$h4(class="modal-title", "Calculation in progress")),
tags$div(
class="modal-body",
shinyWidgets::progressBar(id = "pb", value = 0, display_pct = TRUE)
),
tags$div(class="modal-footer", tags$button(type="button", class="btn btn-default", `data-dismiss`="modal", "Dismiss"))
)
)
)
)
server <- function(input, output, session) {
value <- reactiveVal(0)
observeEvent(input$go, {
shinyWidgets::updateProgressBar(session = session, id = "pb", value = 0) # reinitialize to 0 if you run the calculation several times
session$sendCustomMessage(type = 'launch-modal', "my-modal") # launch the modal
# run calculation
for (i in 1:10) {
Sys.sleep(0.5)
newValue <- value() + 1
value(newValue)
shinyWidgets::updateProgressBar(session = session, id = "pb", value = 100/10*i)
}
Sys.sleep(0.5)
# session$sendCustomMessage(type = 'remove-modal', "my-modal") # hide the modal programmatically
})
}
shinyApp(ui = ui, server = server)

Resources