Features for easier explaining of reactivity

- makeReactiveBinding: Turns a "regular" variable into a reactive.
  No need to use reactiveValues() for simple reactivity.
- setAutoflush (not exported): Causes flushReact() to be called
  each time something is executed at the R console top-level.
- options(shiny.suppressMissingContextError=TRUE): Prevents the
  "Operation not allowed without an active reactive context" error
  when attempting to read a reactive value or expression from the
  console.
This commit is contained in:
Joe Cheng
2013-12-06 11:33:25 -08:00
committed by Joe Cheng
parent b23cc47d95
commit 842765dad0
5 changed files with 129 additions and 3 deletions

View File

@@ -69,6 +69,7 @@ export(is.reactive)
export(is.reactivevalues)
export(isolate)
export(mainPanel)
export(makeReactiveBinding)
export(navbarPage)
export(numericInput)
export(observe)

5
NEWS
View File

@@ -39,6 +39,11 @@ shiny 0.8.0.99
* Added `icon()` function for embedding icons from the
[http://fontawesome.io/](font awesome) icon library
* Added `makeReactiveBinding` function to turn a "regular" variable into a
reactive one (i.e. reading the variable makes the current reactive context
dependent on it, and setting the variable is a source of reactivity).
shiny 0.8.0
--------------------------------------------------------------------------------

View File

@@ -91,9 +91,13 @@ ReactiveEnvironment <- setRefClass(
},
currentContext = function() {
if (is.null(.currentContext)) {
stop('Operation not allowed without an active reactive context. ',
'(You tried to do something that can only be done from inside a ',
'reactive function.)')
if (isTRUE(getOption('shiny.suppressMissingContextError', FALSE))) {
return(getDummyContext())
} else {
stop('Operation not allowed without an active reactive context. ',
'(You tried to do something that can only be done from inside a ',
'reactive expression or observer.)')
}
}
return(.currentContext)
},
@@ -135,3 +139,13 @@ flushReact <- function() {
getCurrentContext <- function() {
.getReactiveEnvironment()$currentContext()
}
getDummyContext <- function() {}
local({
dummyContext <- NULL
getDummyContext <<- function() {
if (is.null(dummyContext))
dummyContext <<- Context$new('[none]', type='isolate')
return(dummyContext)
}
})

View File

@@ -626,6 +626,78 @@ observe <- function(x, env=parent.frame(), quoted=FALSE, label=NULL,
invisible(o)
}
#' Make a reactive variable
#'
#' Turns a normal variable into a reactive variable, that is, one that has
#' reactive semantics when assigned or read in the usual ways. The variable may
#' already exist; if so, its value will be used as the initial value of the
#' reactive variable (or \code{NULL} if the variable did not exist).
#'
#' @param symbol A character string indicating the name of the variable that
#' should be made reactive
#' @param env The environment that will contain the reactive variable
#'
#' @return None.
#'
#' @examples
#' \dontrun{
#' a <- 10
#' makeReactiveBinding("a")
#' b <- reactive(a * -1)
#' observe(print(b))
#' a <- 20
#' }
#' @export
makeReactiveBinding <- function(symbol, env = parent.frame()) {
if (exists(symbol, where = env)) {
initialValue <- get(symbol, pos = env)
do.call(rm, list(symbol, pos = env))
}
else
initialValue <- NULL
values <- reactiveValues(value = initialValue)
makeActiveBinding(symbol, env=env, fun=function(v) {
if (missing(v))
values$value
else
values$value <- v
})
invisible()
}
# `%<-reactive%` <- function(name, value) {
# sym <- deparse(substitute(name))
# assign(sym, value, pos = parent.frame())
# makeReactiveBinding(sym, env=parent.frame())
# invisible(NULL)
# }
# Causes flushReact to be called every time an expression is
# entered into the top-level prompt
setAutoflush <- function(enable) {}
local({
callbackId <- NULL
setAutoflush <<- function(enable) {
if (identical(is.null(callbackId), !isTRUE(enable))) {
return(invisible())
}
if (isTRUE(enable)) {
callbackId <<- addTaskCallback(function(expr, value, ok, visible) {
timerCallbacks$executeElapsed()
flushReact()
return(TRUE)
})
} else {
removeTaskCallback(callbackId)
callbackId <<- NULL
}
invisible()
}
})
# ---------------------------------------------------------------------------
#' Timer

View File

@@ -0,0 +1,34 @@
\name{makeReactiveBinding}
\alias{makeReactiveBinding}
\title{Make a reactive variable}
\usage{
makeReactiveBinding(symbol, env = parent.frame())
}
\arguments{
\item{symbol}{A character string indicating the name of
the variable that should be made reactive}
\item{env}{The environment that will contain the reactive
variable}
}
\value{
None.
}
\description{
Turns a normal variable into a reactive variable, that
is, one that has reactive semantics when assigned or read
in the usual ways. The variable may already exist; if so,
its value will be used as the initial value of the
reactive variable (or \code{NULL} if the variable did not
exist).
}
\examples{
\dontrun{
a <- 10
makeReactiveBinding("a")
b <- reactive(a * -1)
observe(print(b))
a <- 20
}
}