Reputation: 2163
I use Shiny GUI R package. I'm looking for a way to display a message like "loading..." after the actionButton was pressed. The function takes several minutes to execute, so I need to inform the user somehow that the button actually triggered some event. Now the server.R code looks like this:
DATA <- reactive({
if(input$DownloadButton>0) {
RunDownload()
} else {
NULL
}
})
output$Download <- renderText({
if(NROW(DATA())>0) {
paste0(Sys.time(),": ",NROW(DATA()), " items downloaded")
} else {
''
}
})
actionButton()
is a function that downloads data from internet. input$DownloadButton
is actionButton. So after the button was pressed the user waits for several minutes and only then sees a message saying that download was successful. I would like to show a message "Loading..." just after the actionButton was pressed and then another message like paste0(Sys.time(),": ",NROW(DATA()), " items downloaded")
when execution ends.
Upvotes: 68
Views: 42267
Reputation: 5274
Though this question is old I think it is still relevant. I have another solution to offer that displays the activity indicator on the button that starts a lengthy process next to the button label.
We need an action button with a label in a span
and some way of identifying that label.
actionButton("btnUpdate", span("Update", id="UpdateAnimate", class=""))
We also need some CSS animation that can be added to the button label, e.g. like this:
tags$head(tags$style(type="text/css", '
.loading {
display: inline-block;
overflow: hidden;
height: 1.3em;
margin-top: -0.3em;
line-height: 1.5em;
vertical-align: text-bottom;
box-sizing: border-box;
}
.loading.dots::after {
text-rendering: geometricPrecision;
content: "⠋\\A⠙\\A⠹\\A⠸\\A⠼\\A⠴\\A⠦\\A⠧\\A⠇\\A⠏";
animation: spin10 1s steps(10) infinite;
animation-duration: 1s;
animation-timing-function: steps(10);
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: none;
animation-play-state: running;
animation-name: spin10;
}
.loading::after {
display: inline-table;
white-space: pre;
text-align: left;
}
@keyframes spin10 { to { transform: translateY(-15.0em); } }
'))
Now we can use shinyjs
to manipulate the span
class which dynamically adds the animation behind the button label. We add the animation once a user presses the button:
observeEvent(input$btnUpdate, { # User requests update
# ...
shinyjs::addClass(id = "UpdateAnimate", class = "loading dots")
shinyjs::disable("btnUpdate")
# ...
})
When the operation has finished we can remove the class from the span and end the animation:
output$distPlot <- renderPlot({
# ...
Sys.sleep(1) # just for show, you probably want to remove it in a real app
# Button settings
shinyjs::enable("btnUpdate")
shinyjs::removeClass(id = "UpdateAnimate", class = "loading dots")
# ...
})
The full code of the sample app is available as gist on GitHub.
Upvotes: 8
Reputation: 1954
Very simply, you can use built-in shiny functions showModal()
at the start of the function and removeModal()
at the end. If you remove the footer, said modal cannot be clicked out of.
Example:
observeEvent(input$button, {
showModal(modalDialog("Doing a function", footer=NULL))
#Do the stuff here....
#...
#...
#Finish the function
removeModal()
})
Upvotes: 42
Reputation: 3299
You can use ShinyJS: https://github.com/daattali/shinyjs
When the actionButton is pressed, you can easily toggle a text component showing "loading...", and when the calculation is finished, you can then toggle this component to hidden.
Upvotes: 3
Reputation: 2163
I'm already using a simpler and more reliable way than the one I posted before.
A combination of
tags$style(type="text/css", "
#loadmessage {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
padding: 5px 0px 5px 0px;
text-align: center;
font-weight: bold;
font-size: 100%;
color: #000000;
background-color: #CCFF66;
z-index: 105;
}
")
with
conditionalPanel(condition="$('html').hasClass('shiny-busy')",
tags$div("Loading...",id="loadmessage")
)
Example:
runApp(list(
ui = pageWithSidebar(
headerPanel("Test"),
sidebarPanel(
tags$head(tags$style(type="text/css", "
#loadmessage {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
padding: 5px 0px 5px 0px;
text-align: center;
font-weight: bold;
font-size: 100%;
color: #000000;
background-color: #CCFF66;
z-index: 105;
}
")),
numericInput('n', 'Number of obs', 100),
conditionalPanel(condition="$('html').hasClass('shiny-busy')",
tags$div("Loading...",id="loadmessage"))
),
mainPanel(plotOutput('plot'))
),
server = function(input, output) {
output$plot <- renderPlot({ Sys.sleep(2); hist(runif(input$n)) })
}
))
tags$head() is not required, but it's a good practice to keep all the styles inside head tag.
Upvotes: 45
Reputation: 21
I found a solution, that works fine for me. I am using the Bootstrap modal. It is shown when the execution of the function starts and is hidden again, when it ends.
modalBusy <- function(id, title, ...){
msgHandler = singleton(tags$head(tags$script('Shiny.addCustomMessageHandler("jsCode", function(message) { console.log(message) eval(message.code); });' ) ) ) label_id = paste(id, "label", sep='-') modal_tag <- div(id=id, class="modal hide fade", "aria-hidden"=FALSE, "aria-labelledby"=label_id, "role"="dialog", "tabindex"="-1", "data-keyboard"=FALSE, "data-backdrop"="static") header_tag <- div(class="modal-header", h3(id=label_id, title)) body_tag <- div(class="modal-body", Row(...)) footer_tag <- div(class="modal-footer") modal_tag <- tagAppendChildren(modal_tag, header_tag, body_tag, footer_tag) tagList(msgHandler, modal_tag) }
To show and to hide it use the functions
showModal <- function(id,session) {
session$sendCustomMessage(type="jsCode",
list(code= paste("$('#",id,"').modal('show')"
,sep="")))
}
hideModal <- function(id,session) {
session$sendCustomMessage(type="jsCode",
list(code= paste("$('#",id,"').modal('hide')"
,sep="")))
}
Call the showModal function before your function Call and the hideModal function afterwards!
Hope this helps.
Seb
Upvotes: 2
Reputation: 2163
I solved the problem by adding the following code to sidebarPanel():
HTML('<script type="text/javascript">
$(document).ready(function() {
$("#DownloadButton").click(function() {
$("#Download").text("Loading...");
});
});
</script>
')
Upvotes: 4