Added skipFirst arg to observeEvent (#1494)

* added skipFirs arg to observeEvent

* create getCurrentObserver() function

* better NEWS entry

* made code more consistent

* implemented `once` param to `observeEvent`; extensive documentation for `getCurrentObserver`

* implement dig param to `getCurrentObserver`

* fix bug that was causing unit tests to fail

* take two

* git commit

* removed function getCurrentObserver

* delete .globals$currentObserver variable

* update docs

* typo

* remove dupes in index.r (bah humbug)

* rerun devtools::document
This commit is contained in:
Barbara Borges Ribeiro
2016-12-19 23:51:19 +00:00
committed by Joe Cheng
parent 55a16043e1
commit 19623694f5
3 changed files with 245 additions and 56 deletions

View File

@@ -13,6 +13,10 @@ Now there's an official way to slow down reactive values and expressions that in
### Minor new features and improvements
* Addressed [#1486](https://github.com/rstudio/shiny/issue/1486) by adding a new argument to `observeEvent` and `eventReactive`, called `ignoreInit` (defaults to `FALSE` for backwards compatibility). When set to `TRUE`, the action (i.e. the second argument: `handlerExpr` and `valueExpr`, respectively) will not be triggered when the observer/reactive is first created/initialized. In other words, `ignoreInit = TRUE` ensures that the `observeEvent` (or `eventReactive`) is *never* run right away. For more info, see the documentation (`?observeEvent`). ([#1494](https://github.com/rstudio/shiny/pull/1494))
* Added a new argument to `observeEvent` called `once`. When set to `TRUE`, it results in the observer being destroyed (stop observing) after the first time that `handlerExpr` is run (i.e. `once = TRUE` guarantees that the observer only runs, at most, once). For more info, see the documentation (`?observeEvent`). ([#1494](https://github.com/rstudio/shiny/pull/1494))
* Addressed [#1358](https://github.com/rstudio/shiny/issues/1358): more informative error message when calling `runApp()` inside of an app's app.R (or inside ui.R or server.R). ([#1482](https://github.com/rstudio/shiny/pull/1482))
* Added a more descriptive JS warning for `insertUI()` when the selector argument does not match anything in DOM. ([#1488](https://github.com/rstudio/shiny/pull/1488))

View File

@@ -1531,6 +1531,8 @@ maskReactiveContext <- function(expr) {
#' invalidations that come from its reactive dependencies; it only invalidates
#' in response to the given event.
#'
#' @section \code{ignoreNULL} and \code{ignoreInit}:
#'
#' Both \code{observeEvent} and \code{eventReactive} take an \code{ignoreNULL}
#' parameter that affects behavior when the \code{eventExpr} evaluates to
#' \code{NULL} (or in the special case of an \code{\link{actionButton}},
@@ -1543,6 +1545,44 @@ maskReactiveContext <- function(expr) {
#' the action/calculation and just let the user re-initiate it (like a
#' "Recalculate" button).
#'
#' Unlike what happens for \code{ignoreNULL}, only \code{observeEvent} takes in an
#' \code{ignoreInit} argument. By default, \code{observeEvent} will run right when
#' it is created (except if, at that moment, \code{eventExpr} evaluates to \code{NULL}
#' and \code{ignoreNULL} is \code{TRUE}). But when responding to a click of an action
#' button, it may often be useful to set \code{ignoreInit} to \code{TRUE}. For
#' example, if you're setting up an \code{observeEvent} for a dynamically created
#' button, then \code{ignoreInit = TRUE} will guarantee that the action (in
#' \code{handlerExpr}) will only be triggered when the button is actually clicked,
#' instead of also being triggered when it is created/initialized.
#'
#' Even though \code{ignoreNULL} and \code{ignoreInit} can be used for similar
#' purposes they are independent from one another. Here's the result of combining
#' these:
#'
#' \describe{
#' \item{\code{ignoreNULL = TRUE} and \code{ignoreInit = FALSE}}{
#' This is the default. This combination means that \code{handlerExpr} will
#' run every time that \code{eventExpr} is not \code{NULL}. If, at the time
#' of the \code{observeEvent}'s creation, \code{handleExpr} happens to
#' \emph{not} be \code{NULL}, then the code runs.
#' }
#' \item{\code{ignoreNULL = FALSE} and \code{ignoreInit = FALSE}}{
#' This combination means that \code{handlerExpr} will run every time no
#' matter what.
#' }
#' \item{\code{ignoreNULL = FALSE} and \code{ignoreInit = TRUE}}{
#' This combination means that \code{handlerExpr} will \emph{not} run when
#' the \code{observeEvent} is created (because \code{ignoreInit = TRUE}),
#' but it will run every other time.
#' }
#' \item{\code{ignoreNULL = TRUE} and \code{ignoreInit = TRUE}}{
#' This combination means that \code{handlerExpr} will \emph{not} run when
#' the \code{observeEvent} is created (because \code{ignoreInit = TRUE}).
#' After that, \code{handlerExpr} will run every time that \code{eventExpr}
#' is not \code{NULL}.
#' }
#' }
#'
#' @param eventExpr A (quoted or unquoted) expression that represents the event;
#' this can be a simple reactive value like \code{input$click}, a call to a
#' reactive expression like \code{dataset()}, or even a complex expression
@@ -1584,6 +1624,15 @@ maskReactiveContext <- function(expr) {
#' @param ignoreNULL Whether the action should be triggered (or value
#' calculated, in the case of \code{eventReactive}) when the input is
#' \code{NULL}. See Details.
#' @param ignoreInit If \code{TRUE}, then, when this \code{observeEvent} is
#' first created/initialized, ignore the \code{handlerExpr} (the second
#' argument), whether it is otherwise supposed to run or not. The default is
#' \code{FALSE}. See Details.
#' @param once Whether this \code{observeEvent} should be immediately destroyed
#' after the first time that the code in \code{handlerExpr} is run. This
#' pattern is useful when you want to subscribe to a event that should only
#' happen once.
#'
#' @return \code{observeEvent} returns an observer reference class object (see
#' \code{\link{observe}}). \code{eventReactive} returns a reactive expression
#' object (see \code{\link{reactive}}).
@@ -1593,37 +1642,71 @@ maskReactiveContext <- function(expr) {
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' ui <- fluidPage(
#' column(4,
#' numericInput("x", "Value", 5),
#' br(),
#' actionButton("button", "Show")
#'
#' ## App 1: Sample usage
#' shinyApp(
#' ui = fluidPage(
#' column(4,
#' numericInput("x", "Value", 5),
#' br(),
#' actionButton("button", "Show")
#' ),
#' column(8, tableOutput("table"))
#' ),
#' column(8, tableOutput("table"))
#' server = function(input, output) {
#' # Take an action every time button is pressed;
#' # here, we just print a message to the console
#' observeEvent(input$button, {
#' cat("Showing", input$x, "rows\n")
#' })
#' # Take a reactive dependency on input$button, but
#' # not on any of the stuff inside the function
#' df <- eventReactive(input$button, {
#' head(cars, input$x)
#' })
#' output$table <- renderTable({
#' df()
#' })
#' }
#' )
#'
#' ## App 2: Using `once`
#' shinyApp(
#' ui = basicPage( actionButton("go", "Go")),
#' server = function(input, output, session) {
#' observeEvent(input$go, {
#' print(paste("This will only be printed once; all",
#' "subsequent button clicks won't do anything"))
#' }, once = TRUE)
#' }
#' )
#'
#' ## App 3: Using `ignoreInit` and `once`
#' shinyApp(
#' ui = basicPage(actionButton("go", "Go")),
#' server = function(input, output, session) {
#' observeEvent(input$go, {
#' insertUI("#go", "afterEnd",
#' actionButton("dynamic", "click to remove"))
#'
#' # set up an observer that depends on the dynamic
#' # input, so that it doesn't run when the input is
#' # created, and only runs once after that (since
#' # the side effect is remove the input from the DOM)
#' observeEvent(input$dynamic, {
#' removeUI("#dynamic")
#' }, ignoreInit = TRUE, once = TRUE)
#' })
#' }
#' )
#' server <- function(input, output) {
#' # Take an action every time button is pressed;
#' # here, we just print a message to the console
#' observeEvent(input$button, {
#' cat("Showing", input$x, "rows\n")
#' })
#' # Take a reactive dependency on input$button, but
#' # not on any of the stuff inside the function
#' df <- eventReactive(input$button, {
#' head(cars, input$x)
#' })
#' output$table <- renderTable({
#' df()
#' })
#' }
#' shinyApp(ui=ui, server=server)
#' }
#' @export
observeEvent <- function(eventExpr, handlerExpr,
event.env = parent.frame(), event.quoted = FALSE,
handler.env = parent.frame(), handler.quoted = FALSE,
label=NULL, suspended=FALSE, priority=0, domain=getDefaultReactiveDomain(),
autoDestroy = TRUE, ignoreNULL = TRUE) {
label = NULL, suspended = FALSE, priority = 0,
domain = getDefaultReactiveDomain(), autoDestroy = TRUE,
ignoreNULL = TRUE, ignoreInit = FALSE, once = FALSE) {
eventFunc <- exprToFunction(eventExpr, event.env, event.quoted)
if (is.null(label))
@@ -1633,16 +1716,29 @@ observeEvent <- function(eventExpr, handlerExpr,
handlerFunc <- exprToFunction(handlerExpr, handler.env, handler.quoted)
handlerFunc <- wrapFunctionLabel(handlerFunc, "observeEventHandler", ..stacktraceon = TRUE)
invisible(observe({
initialized <- FALSE
o <- observe({
e <- eventFunc()
if (ignoreInit && !initialized) {
initialized <<- TRUE
return()
}
if (ignoreNULL && isNullEvent(e)) {
return()
}
if (once) {
on.exit(o$destroy())
}
isolate(handlerFunc())
}, label = label, suspended = suspended, priority = priority, domain = domain,
autoDestroy = TRUE, ..stacktraceon = FALSE))
autoDestroy = TRUE, ..stacktraceon = FALSE)
invisible(o)
}
#' @rdname observeEvent
@@ -1650,8 +1746,8 @@ observeEvent <- function(eventExpr, handlerExpr,
eventReactive <- function(eventExpr, valueExpr,
event.env = parent.frame(), event.quoted = FALSE,
value.env = parent.frame(), value.quoted = FALSE,
label=NULL, domain=getDefaultReactiveDomain(),
ignoreNULL = TRUE) {
label = NULL, domain = getDefaultReactiveDomain(),
ignoreNULL = TRUE, ignoreInit = FALSE) {
eventFunc <- exprToFunction(eventExpr, event.env, event.quoted)
if (is.null(label))
@@ -1661,13 +1757,17 @@ eventReactive <- function(eventExpr, valueExpr,
handlerFunc <- exprToFunction(valueExpr, value.env, value.quoted)
handlerFunc <- wrapFunctionLabel(handlerFunc, "eventReactiveHandler", ..stacktraceon = TRUE)
initialized <- FALSE
invisible(reactive({
e <- eventFunc()
validate(need(
!ignoreNULL || !isNullEvent(e),
message = FALSE
))
if (ignoreInit && !initialized) {
initialized <<- TRUE
req(FALSE)
}
req(!ignoreNULL || !isNullEvent(e))
isolate(handlerFunc())
}, label = label, domain = domain, ..stacktraceon = FALSE))

View File

@@ -9,11 +9,12 @@ observeEvent(eventExpr, handlerExpr, event.env = parent.frame(),
event.quoted = FALSE, handler.env = parent.frame(),
handler.quoted = FALSE, label = NULL, suspended = FALSE, priority = 0,
domain = getDefaultReactiveDomain(), autoDestroy = TRUE,
ignoreNULL = TRUE)
ignoreNULL = TRUE, ignoreInit = FALSE, once = FALSE)
eventReactive(eventExpr, valueExpr, event.env = parent.frame(),
event.quoted = FALSE, value.env = parent.frame(), value.quoted = FALSE,
label = NULL, domain = getDefaultReactiveDomain(), ignoreNULL = TRUE)
label = NULL, domain = getDefaultReactiveDomain(), ignoreNULL = TRUE,
ignoreInit = FALSE)
}
\arguments{
\item{eventExpr}{A (quoted or unquoted) expression that represents the event;
@@ -61,6 +62,16 @@ automatically destroyed when its domain (if any) ends.}
calculated, in the case of \code{eventReactive}) when the input is
\code{NULL}. See Details.}
\item{ignoreInit}{If \code{TRUE}, then, when this \code{observeEvent} is
first created/initialized, ignore the \code{handlerExpr} (the second
argument), whether it is otherwise supposed to run or not. The default is
\code{FALSE}. See Details.}
\item{once}{Whether this \code{observeEvent} should be immediately destroyed
after the first time that the code in \code{handlerExpr} is run. This
pattern is useful when you want to subscribe to a event that should only
happen once.}
\item{valueExpr}{The expression that produces the return value of the
\code{eventReactive}. It will be executed within an \code{\link{isolate}}
scope.}
@@ -108,6 +119,9 @@ updates in response to an event. This is just like a normal
\link[=reactive]{reactive expression} except it ignores all the usual
invalidations that come from its reactive dependencies; it only invalidates
in response to the given event.
}
\section{\code{ignoreNULL} and \code{ignoreInit}}{
Both \code{observeEvent} and \code{eventReactive} take an \code{ignoreNULL}
parameter that affects behavior when the \code{eventExpr} evaluates to
@@ -120,34 +134,105 @@ wait for the user to initiate the action first (like a "Submit" button);
whereas \code{ignoreNULL=FALSE} is desirable if you want to initially perform
the action/calculation and just let the user re-initiate it (like a
"Recalculate" button).
Unlike what happens for \code{ignoreNULL}, only \code{observeEvent} takes in an
\code{ignoreInit} argument. By default, \code{observeEvent} will run right when
it is created (except if, at that moment, \code{eventExpr} evaluates to \code{NULL}
and \code{ignoreNULL} is \code{TRUE}). But when responding to a click of an action
button, it may often be useful to set \code{ignoreInit} to \code{TRUE}. For
example, if you're setting up an \code{observeEvent} for a dynamically created
button, then \code{ignoreInit = TRUE} will guarantee that the action (in
\code{handlerExpr}) will only be triggered when the button is actually clicked,
instead of also being triggered when it is created/initialized.
Even though \code{ignoreNULL} and \code{ignoreInit} can be used for similar
purposes they are independent from one another. Here's the result of combining
these:
\describe{
\item{\code{ignoreNULL = TRUE} and \code{ignoreInit = FALSE}}{
This is the default. This combination means that \code{handlerExpr} will
run every time that \code{eventExpr} is not \code{NULL}. If, at the time
of the \code{observeEvent}'s creation, \code{handleExpr} happens to
\emph{not} be \code{NULL}, then the code runs.
}
\item{\code{ignoreNULL = FALSE} and \code{ignoreInit = FALSE}}{
This combination means that \code{handlerExpr} will run every time no
matter what.
}
\item{\code{ignoreNULL = FALSE} and \code{ignoreInit = TRUE}}{
This combination means that \code{handlerExpr} will \emph{not} run when
the \code{observeEvent} is created (because \code{ignoreInit = TRUE}),
but it will run every other time.
}
\item{\code{ignoreNULL = TRUE} and \code{ignoreInit = TRUE}}{
This combination means that \code{handlerExpr} will \emph{not} run when
the \code{observeEvent} is created (because \code{ignoreInit = TRUE}).
After that, \code{handlerExpr} will run every time that \code{eventExpr}
is not \code{NULL}.
}
}
}
\examples{
## Only run this example in interactive R sessions
if (interactive()) {
ui <- fluidPage(
column(4,
numericInput("x", "Value", 5),
br(),
actionButton("button", "Show")
## App 1: Sample usage
shinyApp(
ui = fluidPage(
column(4,
numericInput("x", "Value", 5),
br(),
actionButton("button", "Show")
),
column(8, tableOutput("table"))
),
column(8, tableOutput("table"))
server = function(input, output) {
# Take an action every time button is pressed;
# here, we just print a message to the console
observeEvent(input$button, {
cat("Showing", input$x, "rows\\n")
})
# Take a reactive dependency on input$button, but
# not on any of the stuff inside the function
df <- eventReactive(input$button, {
head(cars, input$x)
})
output$table <- renderTable({
df()
})
}
)
## App 2: Using `once`
shinyApp(
ui = basicPage( actionButton("go", "Go")),
server = function(input, output, session) {
observeEvent(input$go, {
print(paste("This will only be printed once; all",
"subsequent button clicks won't do anything"))
}, once = TRUE)
}
)
## App 3: Using `ignoreInit` and `once`
shinyApp(
ui = basicPage(actionButton("go", "Go")),
server = function(input, output, session) {
observeEvent(input$go, {
insertUI("#go", "afterEnd",
actionButton("dynamic", "click to remove"))
# set up an observer that depends on the dynamic
# input, so that it doesn't run when the input is
# created, and only runs once after that (since
# the side effect is remove the input from the DOM)
observeEvent(input$dynamic, {
removeUI("#dynamic")
}, ignoreInit = TRUE, once = TRUE)
})
}
)
server <- function(input, output) {
# Take an action every time button is pressed;
# here, we just print a message to the console
observeEvent(input$button, {
cat("Showing", input$x, "rows\\n")
})
# Take a reactive dependency on input$button, but
# not on any of the stuff inside the function
df <- eventReactive(input$button, {
head(cars, input$x)
})
output$table <- renderTable({
df()
})
}
shinyApp(ui=ui, server=server)
}
}
\seealso{