mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-29 08:48:13 -05:00
530 lines
16 KiB
R
530 lines
16 KiB
R
Dependents <- setRefClass(
|
|
'Dependents',
|
|
fields = list(
|
|
.dependents = 'Map'
|
|
),
|
|
methods = list(
|
|
register = function() {
|
|
ctx <- .getReactiveEnvironment()$currentContext()
|
|
if (!.dependents$containsKey(ctx$id)) {
|
|
.dependents$set(ctx$id, ctx)
|
|
ctx$onInvalidate(function() {
|
|
.dependents$remove(ctx$id)
|
|
})
|
|
}
|
|
},
|
|
invalidate = function() {
|
|
lapply(
|
|
.dependents$values(),
|
|
function(ctx) {
|
|
ctx$invalidate()
|
|
NULL
|
|
}
|
|
)
|
|
}
|
|
)
|
|
)
|
|
|
|
|
|
ReactiveValues <- setRefClass(
|
|
'ReactiveValues',
|
|
fields = list(
|
|
.values = 'environment',
|
|
.dependents = 'environment',
|
|
# Dependents for the list of all names, including hidden
|
|
.namesDeps = 'Dependents',
|
|
# Dependents for all values, including hidden
|
|
.allValuesDeps = 'Dependents',
|
|
# Dependents for all values
|
|
.valuesDeps = 'Dependents'
|
|
),
|
|
methods = list(
|
|
initialize = function() {
|
|
.values <<- new.env(parent=emptyenv())
|
|
.dependents <<- new.env(parent=emptyenv())
|
|
},
|
|
get = function(key) {
|
|
ctx <- .getReactiveEnvironment()$currentContext()
|
|
dep.key <- paste(key, ':', ctx$id, sep='')
|
|
if (!exists(dep.key, where=.dependents, inherits=FALSE)) {
|
|
assign(dep.key, ctx, pos=.dependents, inherits=FALSE)
|
|
ctx$onInvalidate(function() {
|
|
rm(list=dep.key, pos=.dependents, inherits=FALSE)
|
|
})
|
|
}
|
|
|
|
if (!exists(key, where=.values, inherits=FALSE))
|
|
NULL
|
|
else
|
|
base::get(key, pos=.values, inherits=FALSE)
|
|
},
|
|
set = function(key, value) {
|
|
hidden <- substr(key, 1, 1) == "."
|
|
|
|
if (exists(key, where=.values, inherits=FALSE)) {
|
|
if (identical(base::get(key, pos=.values, inherits=FALSE), value)) {
|
|
return(invisible())
|
|
}
|
|
}
|
|
else {
|
|
.namesDeps$invalidate()
|
|
}
|
|
|
|
if (hidden)
|
|
.allValuesDeps$invalidate()
|
|
else
|
|
.valuesDeps$invalidate()
|
|
|
|
assign(key, value, pos=.values, inherits=FALSE)
|
|
dep.keys <- objects(
|
|
pos=.dependents,
|
|
pattern=paste('^\\Q', key, ':', '\\E', '\\d+$', sep=''),
|
|
all.names=TRUE
|
|
)
|
|
lapply(
|
|
mget(dep.keys, envir=.dependents),
|
|
function(ctx) {
|
|
ctx$invalidate()
|
|
NULL
|
|
}
|
|
)
|
|
invisible()
|
|
},
|
|
mset = function(lst) {
|
|
lapply(base::names(lst),
|
|
function(name) {
|
|
.self$set(name, lst[[name]])
|
|
})
|
|
},
|
|
names = function() {
|
|
.namesDeps$register()
|
|
return(ls(.values, all.names=TRUE))
|
|
},
|
|
toList = function(all.names=FALSE) {
|
|
if (all.names)
|
|
.allValuesDeps$register()
|
|
|
|
.valuesDeps$register()
|
|
|
|
return(as.list(.values, all.names=all.names))
|
|
}
|
|
)
|
|
)
|
|
|
|
|
|
# reactivevalues: S3 wrapper class for Values class -----------------------
|
|
|
|
#' Create an object for storing reactive values
|
|
#'
|
|
#' This function returns an object for storing reactive values. It is similar
|
|
#' to a list, but with special capabilities for reactive programming. When you
|
|
#' read a value from it, the calling reactive function takes a reactive
|
|
#' dependency on that value, and when you write to it, it notifies any reactive
|
|
#' functions that depend on that value.
|
|
#'
|
|
#' @examples
|
|
#' # Create the object with no values
|
|
#' values <- reactiveValues()
|
|
#'
|
|
#' # Assign values to 'a' and 'b'
|
|
#' values$a <- 3
|
|
#' values[['b']] <- 4
|
|
#'
|
|
#' \dontrun{
|
|
#' # From within a reactive context, you can access values with:
|
|
#' values$a
|
|
#' values[['a']]
|
|
#' }
|
|
#'
|
|
#' # If not in a reactive context (e.g., at the console), you can use isolate()
|
|
#' # to retrieve the value:
|
|
#' isolate(values$a)
|
|
#' isolate(values[['a']])
|
|
#'
|
|
#' # Set values upon creation
|
|
#' values <- reactiveValues(a = 1, b = 2)
|
|
#' isolate(values$a)
|
|
#'
|
|
#' @param ... Objects that will be added to the reactivevalues object. All of
|
|
#' these objects must be named.
|
|
#'
|
|
#' @seealso \code{\link{isolate}}.
|
|
#'
|
|
#' @export
|
|
reactiveValues <- function(...) {
|
|
args <- list(...)
|
|
if ((length(args) > 0) && (is.null(names(args)) || any(names(args) == "")))
|
|
stop("All arguments passed to reactiveValues() must be named.")
|
|
|
|
values <- .createReactiveValues(ReactiveValues$new())
|
|
|
|
# Use .subset2() instead of [[, to avoid method dispatch
|
|
.subset2(values, 'impl')$mset(args)
|
|
values
|
|
}
|
|
|
|
# Create a reactivevalues object
|
|
#
|
|
# @param values A ReactiveValues object
|
|
# @param readonly Should this object be read-only?
|
|
.createReactiveValues <- function(values = NULL, readonly = FALSE) {
|
|
acc <- list(impl=values)
|
|
class(acc) <- 'reactivevalues'
|
|
attr(acc, 'readonly') <- readonly
|
|
return(acc)
|
|
}
|
|
|
|
#' @S3method $ reactivevalues
|
|
`$.reactivevalues` <- function(x, name) {
|
|
.subset2(x, 'impl')$get(name)
|
|
}
|
|
|
|
#' @S3method [[ reactivevalues
|
|
`[[.reactivevalues` <- `$.reactivevalues`
|
|
|
|
#' @S3method $<- reactivevalues
|
|
`$<-.reactivevalues` <- function(x, name, value) {
|
|
if (attr(x, 'readonly')) {
|
|
stop("Attempted to assign value to a read-only reactivevalues object")
|
|
} else if (length(name) != 1 || !is.character(name)) {
|
|
stop("Must use single string to index into reactivevalues")
|
|
} else {
|
|
.subset2(x, 'impl')$set(name, value)
|
|
x
|
|
}
|
|
}
|
|
|
|
#' @S3method [[<- reactivevalues
|
|
`[[<-.reactivevalues` <- `$<-.reactivevalues`
|
|
|
|
#' @S3method [ reactivevalues
|
|
`[.reactivevalues` <- function(values, name) {
|
|
stop("Single-bracket indexing of reactivevalues object is not allowed.")
|
|
}
|
|
|
|
#' @S3method [<- reactivevalues
|
|
`[<-.reactivevalues` <- function(values, name, value) {
|
|
stop("Single-bracket indexing of reactivevalues object is not allowed.")
|
|
}
|
|
|
|
#' @S3method names reactivevalues
|
|
names.reactivevalues <- function(x) {
|
|
.subset2(x, 'impl')$names()
|
|
}
|
|
|
|
#' @S3method names<- reactivevalues
|
|
`names<-.reactivevalues` <- function(x, value) {
|
|
stop("Can't assign names to reactivevalues object")
|
|
}
|
|
|
|
#' @S3method as.list reactivevalues
|
|
as.list.reactivevalues <- function(x, all.names=FALSE, ...) {
|
|
.Deprecated("reactiveValuesToList",
|
|
msg = paste("'as.list.reactivevalues' is deprecated. ",
|
|
"Use reactiveValuesToList instead.",
|
|
"\nPlease see ?reactiveValuesToList for more information.",
|
|
sep = ""))
|
|
|
|
reactiveValuesToList(x, all.names)
|
|
}
|
|
|
|
#' Convert a reactivevalues object to a list
|
|
#'
|
|
#' This function does something similar to what you might \code{\link{as.list}}
|
|
#' to do. The difference is that the calling context will take dependencies on
|
|
#' every object in the reactivevalues object. To avoid taking dependencies on
|
|
#' all the objects, you can wrap the call with \code{\link{isolate}()}.
|
|
#'
|
|
#' @param x A reactivevalues object.
|
|
#' @param all.names If \code{TRUE}, include objects with a leading dot. If
|
|
#' \code{FALSE} (the default) don't include those objects.
|
|
#' @examples
|
|
#' values <- reactiveValues(a = 1)
|
|
#' \dontrun{
|
|
#' reactiveValuesToList(values)
|
|
#' }
|
|
#'
|
|
#' # To get the objects without taking dependencies on them, use isolate().
|
|
#' # isolate() can also be used when calling from outside a reactive context (e.g.
|
|
#' # at the console)
|
|
#' isolate(reactiveValuesToList(values))
|
|
#'
|
|
#' @export
|
|
reactiveValuesToList <- function(x, all.names=FALSE) {
|
|
.subset2(x, 'impl')$toList(all.names)
|
|
}
|
|
|
|
Observable <- setRefClass(
|
|
'Observable',
|
|
fields = list(
|
|
.func = 'function',
|
|
.label = 'character',
|
|
.dependents = 'Dependents',
|
|
.dirty = 'logical',
|
|
.running = 'logical',
|
|
.value = 'ANY',
|
|
.execCount = 'integer'
|
|
),
|
|
methods = list(
|
|
initialize = function(func, label=deparse(substitute(func))) {
|
|
if (length(formals(func)) > 0)
|
|
stop("Can't make a reactive function from a function that takes one ",
|
|
"or more parameters; only functions without parameters can be ",
|
|
"reactive.")
|
|
.func <<- func
|
|
.dirty <<- TRUE
|
|
.running <<- FALSE
|
|
.label <<- label
|
|
.execCount <<- 0L
|
|
},
|
|
getValue = function() {
|
|
.dependents$register()
|
|
|
|
if (.dirty || .running) {
|
|
.self$.updateValue()
|
|
}
|
|
|
|
if (identical(class(.value), 'try-error'))
|
|
stop(attr(.value, 'condition'))
|
|
return(.value)
|
|
},
|
|
.updateValue = function() {
|
|
ctx <- Context$new(.label)
|
|
ctx$onInvalidate(function() {
|
|
.dirty <<- TRUE
|
|
.dependents$invalidate()
|
|
})
|
|
.execCount <<- .execCount + 1L
|
|
|
|
.dirty <<- FALSE
|
|
|
|
wasRunning <- .running
|
|
.running <<- TRUE
|
|
on.exit(.running <<- wasRunning)
|
|
|
|
ctx$run(function() {
|
|
.value <<- try(.func(), silent=FALSE)
|
|
})
|
|
}
|
|
)
|
|
)
|
|
|
|
#' Create a Reactive Function
|
|
#'
|
|
#' Wraps a normal function to create a reactive function. Conceptually, a
|
|
#' reactive function is a function whose result will change over time.
|
|
#'
|
|
#' Reactive functions are functions that can read reactive values and call other
|
|
#' reactive functions. Whenever a reactive value changes, any reactive functions
|
|
#' that depended on it are marked as "invalidated" and will automatically
|
|
#' re-execute if necessary. If a reactive function is marked as invalidated, any
|
|
#' other reactive functions that recently called it are also marked as
|
|
#' invalidated. In this way, invalidations ripple through the functions that
|
|
#' depend on each other.
|
|
#'
|
|
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{Shiny tutorial} for
|
|
#' more information about reactive functions.
|
|
#'
|
|
#' @param x The value or function to make reactive. The function must not have
|
|
#' any parameters.
|
|
#' @return A reactive function. (Note that reactive functions can only be called
|
|
#' from within other reactive functions.)
|
|
#'
|
|
#' @export
|
|
reactive <- function(x) {
|
|
UseMethod("reactive")
|
|
}
|
|
#' @S3method reactive function
|
|
reactive.function <- function(x) {
|
|
return(Observable$new(x, deparse(substitute(x)))$getValue)
|
|
}
|
|
#' @S3method reactive default
|
|
reactive.default <- function(x) {
|
|
stop("Don't know how to make this object reactive!")
|
|
}
|
|
|
|
# Return the number of times that a reactive function or observer has been run
|
|
execCount <- function(x) {
|
|
if (is.function(x))
|
|
return(environment(x)$.execCount)
|
|
else if (is(x, 'Observer'))
|
|
return(x$.execCount)
|
|
else
|
|
stop('Unexpected argument to execCount')
|
|
}
|
|
|
|
Observer <- setRefClass(
|
|
'Observer',
|
|
fields = list(
|
|
.func = 'function',
|
|
.label = 'character',
|
|
.flushCallbacks = 'list',
|
|
.execCount = 'integer'
|
|
),
|
|
methods = list(
|
|
initialize = function(func, label) {
|
|
if (length(formals(func)) > 0)
|
|
stop("Can't make an observer from a function that takes parameters; ",
|
|
"only functions without parameters can be reactive.")
|
|
|
|
.func <<- func
|
|
.label <<- label
|
|
.execCount <<- 0L
|
|
|
|
# Defer the first running of this until flushReact is called
|
|
ctx <- Context$new(.label)
|
|
ctx$onFlush(function() {
|
|
run()
|
|
})
|
|
ctx$addPendingFlush()
|
|
},
|
|
run = function() {
|
|
ctx <- Context$new(.label)
|
|
|
|
ctx$onInvalidate(function() {
|
|
lapply(.flushCallbacks, function(func) {
|
|
func()
|
|
NULL
|
|
})
|
|
ctx$addPendingFlush()
|
|
})
|
|
|
|
ctx$onFlush(function() {
|
|
run()
|
|
})
|
|
.execCount <<- .execCount + 1L
|
|
ctx$run(.func)
|
|
},
|
|
onInvalidate = function(func) {
|
|
.flushCallbacks <<- c(.flushCallbacks, func)
|
|
}
|
|
)
|
|
)
|
|
|
|
#' Create a reactive observer
|
|
#'
|
|
#' Creates an observer from the given function. An observer is like a reactive
|
|
#' function in that it can read reactive values and call reactive functions, and
|
|
#' will automatically re-execute when those dependencies change. But unlike
|
|
#' reactive functions, it doesn't yield a result and can't be used as an input
|
|
#' to other reactive functions. Thus, observers are only useful for their side
|
|
#' effects (for example, performing I/O).
|
|
#'
|
|
#' Another contrast between reactive functions and observers is their execution
|
|
#' strategy. Reactive functions use lazy evaluation; that is, when their
|
|
#' dependencies change, they don't re-execute right away but rather wait until
|
|
#' they are called by someone else. Indeed, if they are not called then they
|
|
#' will never re-execute. In contrast, observers use eager evaluation; as soon
|
|
#' as their dependencies change, they schedule themselves to re-execute.
|
|
#'
|
|
#' @param func The function to observe. It must not have any parameters. Any
|
|
#' return value from this function will be ignored.
|
|
#'
|
|
#' @export
|
|
observe <- function(func) {
|
|
invisible(Observer$new(func, deparse(substitute(func))))
|
|
}
|
|
|
|
#' Timer
|
|
#'
|
|
#' Creates a reactive timer with the given interval. A reactive timer is like a
|
|
#' reactive value, except reactive values are triggered when they are set, while
|
|
#' reactive timers are triggered simply by the passage of time.
|
|
#'
|
|
#' \link[=reactive]{Reactive functions} and observers that want to be
|
|
#' invalidated by the timer need to call the timer function that
|
|
#' \code{reactiveTimer} returns, even if the current time value is not actually
|
|
#' needed.
|
|
#'
|
|
#' See \code{\link{invalidateLater}} as a safer and simpler alternative.
|
|
#'
|
|
#' @param intervalMs How often to fire, in milliseconds
|
|
#' @return A no-parameter function that can be called from a reactive context,
|
|
#' in order to cause that context to be invalidated the next time the timer
|
|
#' interval elapses. Calling the returned function also happens to yield the
|
|
#' current time (as in \code{\link{Sys.time}}).
|
|
#' @seealso invalidateLater
|
|
#' @export
|
|
reactiveTimer <- function(intervalMs=1000) {
|
|
dependents <- Map$new()
|
|
timerCallbacks$schedule(intervalMs, function() {
|
|
timerCallbacks$schedule(intervalMs, sys.function())
|
|
lapply(
|
|
dependents$values(),
|
|
function(dep.ctx) {
|
|
dep.ctx$invalidate()
|
|
NULL
|
|
})
|
|
})
|
|
return(function() {
|
|
ctx <- .getReactiveEnvironment()$currentContext()
|
|
if (!dependents$containsKey(ctx$id)) {
|
|
dependents$set(ctx$id, ctx)
|
|
ctx$onInvalidate(function() {
|
|
dependents$remove(ctx$id)
|
|
})
|
|
}
|
|
return(Sys.time())
|
|
})
|
|
}
|
|
|
|
#' Scheduled Invalidation
|
|
#'
|
|
#' Schedules the current reactive context to be invalidated in the given number
|
|
#' of milliseconds.
|
|
#' @param millis Approximate milliseconds to wait before invalidating the
|
|
#' current reactive context.
|
|
#' @export
|
|
invalidateLater <- function(millis) {
|
|
ctx <- .getReactiveEnvironment()$currentContext()
|
|
timerCallbacks$schedule(millis, function() {
|
|
ctx$invalidate()
|
|
})
|
|
invisible()
|
|
}
|
|
|
|
#' Create a non-reactive scope for an expression
|
|
#'
|
|
#' Executes the given expression in a scope where reactive values or functions
|
|
#' can be read, but they cannot cause the reactive scope of the caller to be
|
|
#' re-evaluated when they change.
|
|
#'
|
|
#' Ordinarily, the simple act of reading a reactive value causes a relationship
|
|
#' to be established between the caller and the reactive value, where a change
|
|
#' to the reactive value will cause the caller to re-execute. (The same applies
|
|
#' for the act of getting a reactive function's value.) The \code{isolate}
|
|
#' function lets you read a reactive value or function without establishing this
|
|
#' relationship.
|
|
#'
|
|
#' @param expr An expression that can access reactive values or functions.
|
|
#'
|
|
#' @examples
|
|
#' \dontrun{
|
|
#' observer(function() {
|
|
#' input$saveButton # Do take a dependency on input$saveButton
|
|
#'
|
|
#' # isolate a simple expression
|
|
#' data <- get(isolate(input$dataset)) # No dependency on input$dataset
|
|
#' writeToDatabase(data)
|
|
#' })
|
|
#'
|
|
#' observer(function() {
|
|
#' input$saveButton # Do take a dependency on input$saveButton
|
|
#'
|
|
#' # isolate a whole block
|
|
#' data <- isolate({
|
|
#' a <- input$valueA # No dependency on input$valueA or input$valueB
|
|
#' b <- input$valueB
|
|
#' c(a=a, b=b)
|
|
#' })
|
|
#' writeToDatabase(data)
|
|
#' })
|
|
#' }
|
|
#' @export
|
|
isolate <- function(expr) {
|
|
ctx <- Context$new('[isolate]')
|
|
ctx$run(function() {
|
|
eval.parent(expr)
|
|
})
|
|
}
|