Thibault Senegas
Thibault Senegas

Reputation: 35

Re init countdown in a shiny app, keep previous countdown hidden

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

Answers (1)

Justin Landis
Justin Landis

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!"
          ))
        }
      }
    })
  })
}

What changed

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

Related Questions