Skip to contents

Call this once, near the top of your server function. After that, rewind records the session inputs as the user works. It also records the reactive values that you register with rewind_track(). The user can then move backwards and forwards through that history.

Usage

rewind_enable(
  session = shiny::getDefaultReactiveDomain(),
  inputs = NULL,
  exclude = NULL,
  depth = 50L,
  coalesce_ms = 400L,
  restore_timeout = 2,
  shortcuts = TRUE,
  verbose = FALSE
)

Arguments

session

The Shiny session. The default is the current session.

inputs

Character vector of the input IDs to capture. Use NULL (the default) to capture all permitted inputs.

exclude

Character vector of input IDs to skip. rewind applies this after inputs.

depth

The maximum number of history entries to keep. rewind removes the oldest entries first.

coalesce_ms

The quiet period in milliseconds before rewind writes a change to the history. Increase it to group more changes.

restore_timeout

The time in seconds that rewind waits for the browser to finish a restore. The default of 2 suits a fast connection.

Increase it for an application on a slow connection, such as one behind a VPN or on a mobile network. If the limit is too short, capture starts again while the browser is still applying the restore, and a partial state becomes a history entry that the user never made.

Do not increase it more than you need. If a restore never returns, rewind ignores the changes of the user until this limit ends.

shortcuts

Set to TRUE to bind the keyboard shortcuts in the browser. There are three:

  • Ctrl + Z (Cmd + Z on macOS) does an undo;

  • Ctrl + Shift + Z (Cmd + Shift + Z on macOS) does a redo;

  • Ctrl + Y also does a redo. This is the usual redo shortcut on Windows.

The shortcuts do nothing while the user types in a text field. The text undo of the browser thus continues to work.

verbose

Set to TRUE to show messages about what rewind captures and restores. This is useful during development.

Value

The controller, invisibly. Most applications can ignore it.

What gets captured

By default rewind captures every input in the session. There are five exclusions:

  • action buttons and links. Their value is a click counter.

  • shiny::fileInput(). Its value points to a temporary file on the server. Shiny deletes that file at the next upload. An old snapshot would thus point to a file that does not exist.

  • password fields, such as shiny::passwordInput(). A password must not be kept in the history, put back in a field by an undo, or shown by rewind_diff(). The server cannot tell a password from other text, so the browser reports which fields are passwords, including fields that renderUI() adds later. This exclusion applies even when inputs names the field. In shiny::testServer() there is no browser, so a password field there is captured like any other input.

  • inputs with names that start with rewind_. These belong to this package.

  • inputs with names that start with .. These are internal to Shiny.

Text inputs

A word typed at a normal speed is one step. A pause longer than coalesce_ms starts a new step, so slow typing can give several. For a text box, textInput(updateOn = "blur") gives exactly one step for each edit, when the user leaves the box.

While the cursor is in a text box, Ctrl + Z is the browser's own text undo, and rewind does nothing (refer to shortcuts below). The browser's undo still changes the box, so rewind records the change as a new step. To step back through the history, leave the box first, or use the buttons.

Use inputs to give a list of the inputs to capture. This is usually better in a large application. Undo must move the controls that the user thinks of as filters. It must not move every other input on the page.

Grouping

Changes that occur within coalesce_ms of each other become one history entry. One drag of a slider is thus one undo step, not forty. Use rewind_step() to group changes yourself.

Modules

You can call this function inside a moduleServer(). rewind captures the inputs with their module-local names. These are the same names that input$ uses inside the module. rewind adds the namespace with session$ns() when it restores them.

All modules share session$userData. A second call to rewind_enable() thus uses the same history as the first call. It does not make a second history. Call the function once, at the position in the module tree that is best for your application.

Examples

if (interactive()) {
  library(shiny)

  ui <- fluidPage(
    rewind_buttons(),
    selectInput("species", "Species", c("setosa", "versicolor", "virginica")),
    sliderInput("n", "Rows", 1, 50, 10),
    tableOutput("tbl")
  )

  server <- function(input, output, session) {
    rewind_enable()

    output$tbl <- renderTable({
      head(iris[iris$Species == input$species, ], input$n)
    })
  }

  shinyApp(ui, server)
}