1

I built an app in R Shiny which uses time series data that excludes many dates. Within the app a user can select a new dataset, so the dates available will change. I'm using updateDateInput to update the dateInput selector. However, updateDateInput does not seem to allow the datesdisabled function?

Here is a reprex:

library(shiny)

# Sample 3 dates and disable the rest
 
my_dates <- sample(seq(as.Date('2021-01-01'), as.Date('2021-01-31'), by = "day"), 3)    
date_choices <- seq.Date(from = min(my_dates), to = max(my_dates), by = 1)
dates_disabled <- date_choices[!(date_choices %in% my_dates)]

ui <- fluidPage(
    dateInput("date", "Select Date",
              min = min(date_choices),
              max = max(date_choices),
              value = max(date_choices),
              datesdisabled = dates_disabled),
    actionButton("click", "Click Me")
)

server <- function(input, output, session) {
    observeEvent(input$click, {
        my_dates <- sample(seq(as.Date('2021-01-01'), as.Date('2021-01-31'), by = "day"), 3)
        date_choices <- seq.Date(from = min(my_dates), to = max(my_dates), by = 1)
        dates_disabled <- date_choices[!(date_choices %in% my_dates)]
        updateDateInput(
            session, 
            "date",
            min = min(date_choices),
            max = max(date_choices),
            value = max(date_choices),
            datesdisabled = dates_disabled)
    })
}

shinyApp(ui, server)

When the button is clicked and the updateDateInput runs, I get this error:

Warning: Error in updateDateInput: unused argument (datesdisabled = dates_disabled)

I guess there is the option of changing the date to a character and using selectInput? But then I don't get the nice calendar!

Eric Krantz
  • 1,854
  • 15
  • 25

1 Answers1

2

You are right, the datesdisabled argument is not available in the update function. You can change the disabled dates by moving the UI declaration into the server and feed it to the client with renderUI().

The sample does not declare the date input in the UI but a uiOutput("date"). The server can dynamically create the dateInput using the datesdisabled argument. This way you can change the disabled dates.

The example will pick only 3 enabled dates after every button click.


# Reprex: The actual implementation of this uses data from a file:
#    1. Reads data file before ui and server are established
#    2. Does a bunch of calculations
#    3. Identifies dates that exist in data file
#    4. The data file is getting updated in the background from another application.
#    5. Allows user to click the button to update the data file. Reprex shows code
#       that is used to update the date selector based on new data read. Dates are 
#       random in reprex, but would come from data file in actual code.

# Sample 3 dates and disable the rest - actual code reads data file here
#   and parses out dates that exist in records

my_dates <- seq(as.Date('2021-01-01'), as.Date('2021-01-31'), by = "day")
date_choices <- sample(my_dates, 31-3)

ui <- fluidPage(
    uiOutput("date"), textOutput("disabled"),
    actionButton("click", "Click Me")
)

server <- function(input, output, session) {
    dates_disabled <- reactiveVal(NULL)
    
    # Init 'dates_disabled()' once before Shiny flushes the reactive system with callback,
    #   using date_choices that exist in original data set

    onFlush(fun = function () {dates_disabled(date_choices)}, once = TRUE)
    
    # dateInput widget
    output$date <- renderUI({
        maxDate <- as.Date(max(setdiff(my_dates, dates_disabled())),
                           origin = "1970-01-01")
        dateInput(input = "date", 
                  label = "Select Date",
                  min = min(my_dates),
                  max = max(my_dates),
                  value = maxDate,
                  datesdisabled = dates_disabled())
    })
    
    # This output makes it easier to test if it works by showing the enabled dates
    output$disabled <- renderPrint({
        req(dates_disabled()) # only run this when 'dates_disabled' is initialized properly
        Enabled <- as.Date(setdiff(seq(as.Date('2021-01-01'), as.Date('2021-01-31'), by = "day"), 
                                   dates_disabled()), 
                           origin = '1970-01-01')
        paste("Enabled:", paste(Enabled[order(Enabled)], collapse = ", "))
    })
    
    # Set new datesdisabled on button click
    #    Actual code would read updated data file and parse new dates
    observeEvent(input$click, {
        SelectedDates <- sample(my_dates, 31-3)
        dates_disabled( SelectedDates )
    })
}

shinyApp(ui, server)
Eric Krantz
  • 1,854
  • 15
  • 25
Jan
  • 4,974
  • 3
  • 26
  • 43
  • This is very close. Thanks you! However, when the app is initialized, all of the dates are available instead of just the 3 "original" dates. I need the `datesdisabled` to run when loading the app. Note that the button click in the new app actually loads a new dataframe, so I don't want to simulate a button click in the server. – Eric Krantz Jan 29 '21 at 20:26
  • I guess what I'm asking is: Is there a way to call `renderUI` once when the app runs, without clicking the button or simulating a button click? – Eric Krantz Jan 29 '21 at 20:36
  • If the data comes from a file a `reactiveFileReader` might be interesting. But only when the file does not only initialize the `dates_disabled()`. In my sample I used a way to initialize it with using an `onFlush` callback. With `once = TRUE` it will be run the first time enabling the 1st, 15th. and 31st of January. – Jan Jan 30 '21 at 09:34
  • Of course, you can force the `renderUI` by changing any reactive expression that it uses. In my sample any change in `dates_disabled` makes sure that the UI is rendered again. – Jan Jan 30 '21 at 09:38
  • I made changes: (1) input$date shows in the output box (otherwise the user is confused), and (2) changed `onFlush` so the initial date choices are from what exists in the file when the app is run. I considered `reactiveFileReader` but (1) I would still have the issue of updating the calendar dates with `datesdisabled`, and (2) the app is heavy in calculations so runs kind of slow, and I don't want to add any additional lag time (the user will likely only click the button to update the data once or twice per session). – Eric Krantz Jan 30 '21 at 19:31
  • I did an edit to the code (as mentioned above) and will accept the answer after the edit is accepted. Thank you, this helps a lot! – Eric Krantz Jan 30 '21 at 19:42