Reputation: 35
I got an issue with a countdown that i try to implement into a shiny App.
When I click on the "Init" button, i would like that the countdown start. But if I click again on the Init button before the end of the countdown I want that the countdown re-init.
It works visually, but in the background, there is two countdown running. I want to avoid that.
Below is a reproducible exemple of my issue
library(lubridate)
library(shiny)
ui <- fluidPage(
hr(),
actionButton('init','Init'),
textOutput('timeleft')
)
server <- function(input, output, session) {
observeEvent(input$init, {
# Initialize the timer, 10 seconds, not active.
timer <- reactiveVal(3 * 60 - as.integer(lubridate::as.duration(lubridate::interval(as.POSIXct(now(tzone = "America/Montreal") - 160), lubridate::now(tzone = "America/Montreal")))))
active <- reactiveVal(TRUE)
# Output the time left.
output$timeleft <- renderText({
paste("Time left: ", seconds_to_period(timer()))
})
# observer that invalidates every second. If timer is active, decrease by one.
observe({
invalidateLater(1000, session)
isolate({
if(active())
{
timer(timer()-1)
if(timer()<1)
{
active(FALSE)
showModal(modalDialog(
title = "Important message",
"Countdown completed!"
))
}
}
})
})
})
}
shinyApp(ui, server)
Upvotes: 0
Views: 80
Reputation: 2071
See if this cleans up the server side.
server <- function(input, output, session) {
# Initialize the timer, 10 seconds, not active.
timer <- reactiveVal(3 * 60 - as.integer(lubridate::as.duration(lubridate::interval(as.POSIXct(now(tzone = "America/Montreal") - 160), lubridate::now(tzone = "America/Montreal")))))
active <- reactiveVal(TRUE)
observeEvent(input$init, {
# Output the time left.
output$timeleft <- renderText({
paste("Time left: ", seconds_to_period(timer()))
})
timer(3 * 60 - as.integer(lubridate::as.duration(lubridate::interval(as.POSIXct(now(tzone = "America/Montreal") - 160), lubridate::now(tzone = "America/Montreal")))))
active(TRUE)
})
# observer that invalidates every second. If timer is active, decrease by one.
observe({
input$init
invalidateLater(1000, session)
isolate({
if(active())
{
timer(timer()-1)
if(timer()<1)
{
active(FALSE)
showModal(modalDialog(
title = "Important message",
"Countdown completed!"
))
}
}
})
})
}
Instead of defining the observe
within an observeEvent
call (which would made a new observer each time input$init
is called), the observe
is made once and now will be invalidated when the user presses the input$init
button. The observeEvent
observer now just resets the reactive values timer()
and active()
.
Upvotes: 1