--- title: "Snapshots and bookmarks" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Snapshots and bookmarks} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` Shiny's bookmarking (`enableBookmarking()`) and shinysnap solve neighbouring problems, and an app can use both. This vignette compares them, shows how they coexist, and lists what to change when moving an app's save-and-restore feature from one to the other. ## What each one does | | bookmarking | shinysnap | |---|---|---| | state lives in | a URL (`"url"`) or a server directory (`"server"`) | a file the user downloads and uploads | | restore happens | by loading the URL: a new session, a page reload | into the running session, no reload | | dynamic UI | restored at construction through `restoreInput()` | restored at construction *and* by the client as inputs appear | | requires | a UI function, `enableBookmarking()` | nothing in the UI | | readable by people | no (URL-encoded JSON, or `.rds` files) | yes (JSON) | | survives app changes | no built-in help | app name and version in the file, `validate` and `migrate` hooks, a report of what did not apply | Bookmarking is the right tool for "send a colleague a link to what I am looking at". shinysnap is the right tool for "save my work to a file, come back next month, possibly on a newer version of the app". ## Coexistence shinysnap reuses the parts of bookmarking's machinery that make sense mid-session, so an app that already bookmarks keeps working: - Ids excluded with `setBookmarkExclude()` are excluded from snapshots too. - Values that shiny's serializers mark as unserializable (passwords, and anything registered with `setSerializer()`) are never captured. - During a restore, shinysnap primes the session's restore context, the object behind `restoreInput()`, with the snapshot's values, and puts the previous context back when the restore settles. - shinysnap's own internal inputs are marked unserializable, so they never show up in a bookmark URL. The hooks mirror each other. `onBookmark(function(state) ...)` writes into `state$values`; so does `snap_on_save()`. `onRestore()` reads `state$values`; `snap_on_restore()` reads the file's `values`, and `snap_track()` writes them back into your `reactiveValues` for you. ## Turning a snapshot into a bookmark `snap_as_bookmark_url()` encodes a snapshot the way URL bookmarking does, so that a saved state can also be opened as a link, provided the app has `enableBookmarking("url")` and a UI function: ```{r} library(shinysnap) snap <- list( inputs = list(n = 100L, model = "complex", weights = c(0.5, 0.75)), values = list(note = "baseline") ) snap_as_bookmark_url(snap, base_url = "https://example.org/app/") ``` Inside a server function, pass `session` instead of `base_url` and the protocol, host, port, and path the browser used are filled in. The query string carries the same `_inputs_` and `_values_` keys, with the same encoding, as the URL `session$doBookmark()` produces for the same state. ## Migrating an app 1. Replace `enableBookmarking()` and the `bookmarkButton()` with `snap_download_button()` and `snap_file_input()` in the UI, and `snap_download_handler()` and `snap_file_restore()` in the server. The UI no longer needs to be a function. 2. Replace `onBookmark()` hooks with `snap_on_save()`, and `onRestore()` hooks with `snap_track()` for `reactiveValues` (they are written back for you) or `snap_on_restore()` for anything else. 3. Keep `setBookmarkExclude()` calls; add `snap_enable(exclude = ...)` patterns for ids that should never be saved. 4. Give the app a name and a version with `snap_enable()`, and write a `migrate` hook the first time a saved value changes meaning. 5. For tests, `snap_as_test_inputs()` turns a saved file into the `session$setInputs()` call of a `shiny::testServer()` test.