1.globals$options <- list()
2
3#' @param name Name of an option to get.
4#' @param default Value to be returned if the option is not currently set.
5#' @rdname shinyOptions
6#' @export
7getShinyOption <- function(name, default = NULL) {
8  # Make sure to use named (not numeric) indexing
9  name <- as.character(name)
10
11  # Check if there's a current session
12  session <- getDefaultReactiveDomain()
13  if (!is.null(session)) {
14    if (name %in% names(session$options)) {
15      return(session$options[[name]])
16    } else {
17      return(default)
18    }
19  }
20
21  # Check if there's a current app
22  app_state <- getCurrentAppState()
23  if (!is.null(app_state)) {
24    if (name %in% names(app_state$options)) {
25      return(app_state$options[[name]])
26    } else {
27      return(default)
28    }
29  }
30
31  # If we got here, look in global options
32  if (name %in% names(.globals$options)) {
33    return(.globals$options[[name]])
34  } else {
35    return(default)
36  }
37}
38
39#' Get or set Shiny options
40#'
41#' @description
42#'
43#' There are two mechanisms for working with options for Shiny. One is the
44#' [options()] function, which is part of base R, and the other is the
45#' `shinyOptions()` function, which is in the Shiny package. The reason for
46#' these two mechanisms is has to do with legacy code and scoping.
47#'
48#' The [options()] function sets options globally, for the duration of the R
49#' process. The [getOption()] function retrieves the value of an option. All
50#' shiny related options of this type are prefixed with `"shiny."`.
51#'
52#' The `shinyOptions()` function sets the value of a shiny option, but unlike
53#' `options()`, it is not always global in scope; the options may be scoped
54#' globally, to an application, or to a user session in an application,
55#' depending on the context. The `getShinyOption()` function retrieves a value
56#' of a shiny option. Currently, the options set via `shinyOptions` are for
57#' internal use only.
58#'
59#' @section Options with `options()`:
60#'
61#' \describe{
62#' \item{shiny.autoreload (defaults to `FALSE`)}{If `TRUE` when a Shiny app is launched, the
63#'   app directory will be continually monitored for changes to files that
64#'   have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. If any
65#'   changes are detected, all connected Shiny sessions are reloaded. This
66#'   allows for fast feedback loops when tweaking Shiny UI.
67#'
68#'   Since monitoring for changes is expensive (we simply poll for last
69#'   modified times), this feature is intended only for development.
70#'
71#'   You can customize the file patterns Shiny will monitor by setting the
72#'   shiny.autoreload.pattern option. For example, to monitor only ui.R:
73#'   `options(shiny.autoreload.pattern = glob2rx("ui.R"))`
74#'
75#'   The default polling interval is 500 milliseconds. You can change this
76#'   by setting e.g. `options(shiny.autoreload.interval = 2000)` (every
77#'   two seconds).}
78#' \item{shiny.deprecation.messages (defaults to `TRUE`)}{This controls whether messages for
79#'   deprecated functions in Shiny will be printed. See
80#'   [shinyDeprecated()] for more information.}
81#' \item{shiny.error (defaults to `NULL`)}{This can be a function which is called when an error
82#'   occurs. For example, `options(shiny.error=recover)` will result a
83#'   the debugger prompt when an error occurs.}
84#' \item{shiny.fullstacktrace (defaults to `FALSE`)}{Controls whether "pretty" (`FALSE`) or full
85#'   stack traces (`TRUE`) are dumped to the console when errors occur during Shiny app execution.
86#'   Pretty stack traces attempt to only show user-supplied code, but this pruning can't always
87#'   be done 100% correctly.}
88#' \item{shiny.host (defaults to `"127.0.0.1"`)}{The IP address that Shiny should listen on. See
89#'   [runApp()] for more information.}
90#' \item{shiny.jquery.version (defaults to `3`)}{The major version of jQuery to use.
91#'   Currently only values of `3` or `1` are supported. If `1`, then jQuery 1.12.4 is used. If `3`,
92#'   then jQuery `r version_jquery` is used.}
93#' \item{shiny.json.digits (defaults to `16`)}{The number of digits to use when converting
94#'   numbers to JSON format to send to the client web browser.}
95#' \item{shiny.launch.browser (defaults to `interactive()`)}{A boolean which controls the default behavior
96#'   when an app is run. See [runApp()] for more information.}
97#' \item{shiny.maxRequestSize (defaults to 5MB)}{This is a number which specifies the maximum
98#'   web request size, which serves as a size limit for file uploads.}
99#' \item{shiny.minified (defaults to `TRUE`)}{By default
100#'   Whether or not to include Shiny's JavaScript as a minified (`shiny.min.js`)
101#'   or un-minified (`shiny.js`) file. The un-minified version is larger,
102#'   but can be helpful for development and debugging.}
103#' \item{shiny.port (defaults to a random open port)}{A port number that Shiny will listen on. See
104#'   [runApp()] for more information.}
105#' \item{shiny.reactlog (defaults to `FALSE`)}{If `TRUE`, enable logging of reactive events,
106#'   which can be viewed later with the [reactlogShow()] function.
107#'   This incurs a substantial performance penalty and should not be used in
108#'   production.}
109#' \item{shiny.sanitize.errors (defaults to `FALSE`)}{If `TRUE`, then normal errors (i.e.
110#'   errors not wrapped in `safeError`) won't show up in the app; a simple
111#'   generic error message is printed instead (the error and strack trace printed
112#'   to the console remain unchanged). If you want to sanitize errors in general, but you DO want a
113#'   particular error `e` to get displayed to the user, then set this option
114#'   to `TRUE` and use `stop(safeError(e))` for errors you want the
115#'   user to see.}
116#' \item{shiny.stacktraceoffset (defaults to `TRUE`)}{If `TRUE`, then Shiny's printed stack
117#'   traces will display srcrefs one line above their usual location. This is
118#'   an arguably more intuitive arrangement for casual R users, as the name
119#'   of a function appears next to the srcref where it is defined, rather than
120#'   where it is currently being called from.}
121#' \item{shiny.suppressMissingContextError (defaults to `FALSE`)}{Normally, invoking a reactive
122#'   outside of a reactive context (or [isolate()]) results in
123#'   an error. If this is `TRUE`, don't error in these cases. This
124#'   should only be used for debugging or demonstrations of reactivity at the
125#'   console.}
126#' \item{shiny.testmode (defaults to `FALSE`)}{If `TRUE`, then various features for testing Shiny
127#'   applications are enabled.}
128#' \item{shiny.trace (defaults to `FALSE`)}{Print messages sent between the R server and the web
129#'   browser client to the R console. This is useful for debugging. Possible
130#'   values are `"send"` (only print messages sent to the client),
131#'   `"recv"` (only print messages received by the server), `TRUE`
132#'   (print all messages), or `FALSE` (default; don't print any of these
133#'   messages).}
134#' \item{shiny.autoload.r (defaults to `TRUE`)}{If `TRUE`, then the R/
135#'   of a shiny app will automatically be sourced.}
136#' \item{shiny.usecairo (defaults to `TRUE`)}{This is used to disable graphical rendering by the
137#'   Cairo package, if it is installed. See [plotPNG()] for more
138#'   information.}
139#' \item{shiny.devmode (defaults to `NULL`)}{Option to enable Shiny Developer Mode. When set,
140#'   different default `getOption(key)` values will be returned. See [devmode()] for more details.}
141### Not documenting as 'shiny.devmode.verbose' is for niche use only
142# ' \item{shiny.devmode.verbose (defaults to `TRUE`)}{If `TRUE`, will display messages printed
143# '   about which options are being set. See [devmode()] for more details. }
144### (end not documenting 'shiny.devmode.verbose')
145#' }
146#'
147#'
148#' @section Scoping for `shinyOptions()`:
149#'
150#'   There are three levels of scoping for `shinyOptions()`: global,
151#'   application, and session.
152#'
153#'   The global option set is available by default. Any calls to
154#'   `shinyOptions()` and `getShinyOption()` outside of an app will access the
155#'   global option set.
156#'
157#'   When a Shiny application is run with [runApp()], the global option set is
158#'   duplicated and the new option set is available at the application level. If
159#'   options are set from `global.R`, `app.R`, `ui.R`, or `server.R` (but
160#'   outside of the server function), then the application-level options will be
161#'   modified.
162#'
163#'   Each time a user session is started, the application-level option set is
164#'   duplicated, for that session. If the options are set from inside the server
165#'   function, then they will be scoped to the session.
166#'
167#' @section Options with `shinyOptions()`:
168#'
169#'   There are a number of global options that affect Shiny's behavior. These
170#'   can be set globally with `options()` or locally (for a single app) with
171#'   `shinyOptions()`.
172#'
173#'   \describe{ \item{cache}{A caching object that will be used by
174#'   [renderCachedPlot()]. If not specified, a [cachem::cache_mem()] will be
175#'   used.} }
176#'
177#' @param ... Options to set, with the form `name = value`.
178#' @aliases shiny-options
179#' @export
180shinyOptions <- function(...) {
181  newOpts <- list2(...)
182
183  if (length(newOpts) > 0) {
184    # If we're within a session, modify at the session level.
185    session <- getDefaultReactiveDomain()
186    if (!is.null(session)) {
187      # Modify session-level-options
188      session$options <- dropNulls(mergeVectors(session$options, newOpts))
189      return(invisible(session$options))
190    }
191
192    # If not in a session, but we have a currently running app, modify options
193    # at the app level.
194    app_state <- getCurrentAppState()
195    if (!is.null(app_state)) {
196      # Modify app-level options
197      app_state$options <- dropNulls(mergeVectors(app_state$options, newOpts))
198      return(invisible(app_state$options))
199    }
200
201    # If no currently running app, modify global options and return them.
202    .globals$options <- dropNulls(mergeVectors(.globals$options, newOpts))
203    return(invisible(.globals$options))
204  }
205
206  # If not setting any options, just return current option set, visibly.
207
208  session <- getDefaultReactiveDomain()
209  if (!is.null(session)) {
210    return(session$options)
211  }
212
213  app_state <- getCurrentAppState()
214  if (!is.null(app_state)) {
215    return(app_state$options)
216  }
217
218  return(.globals$options)
219}
220
221
222# Get specific shiny options and put them in a list, reset those shiny options,
223# and then return the options list. This should be during the creation of a
224# shiny app object. This function "consumes" the options when the shinyApp
225# object is created, so the options won't affect another app that is created
226# later.
227#
228# ==== Example ====
229# shinyOptions(bookmarkStore = 1234)
230# # This now returns 1234.
231# getShinyOption("bookmarkStore")
232#
233# # Creating the app captures the bookmarkStore option and clears it.
234# s <- shinyApp(
235#   fluidPage(verbatimTextOutput("txt")),
236#   function(input, output) {
237#     output$txt <- renderText(getShinyOption("bookmarkStore"))
238#   }
239# )
240#
241# # This now returns NULL.
242# getShinyOption("bookmarkStore")
243#
244# When running the app, the app will display "1234"
245# runApp(s)
246#
247# # After quitting the app, this still returns NULL.
248# getShinyOption("bookmarkStore")
249# ==================
250#
251# If another app had been created after s was created, but before s was run,
252# then it would capture the value of "bookmarkStore" at the time of creation.
253captureAppOptions <- function() {
254  options <- list(
255    appDir = getwd(),
256    bookmarkStore = getShinyOption("bookmarkStore")
257  )
258
259  shinyOptions(appDir = NULL, bookmarkStore = NULL)
260
261  options
262}
263
264# Do the inverse of captureAppOptions. This should be called once the app is
265# started.
266applyCapturedAppOptions <- function(options) {
267  if (!is.null(options)) {
268    do.call(shinyOptions, options)
269  }
270}
271