mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 07:58:11 -05:00
Compare commits
169 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a141f08298 | ||
|
|
cbcf9ce645 | ||
|
|
4e71b9576d | ||
|
|
f36567a5cd | ||
|
|
924ebb6c7f | ||
|
|
6e7e8eb44a | ||
|
|
308c583254 | ||
|
|
97b2f7e5ca | ||
|
|
3ea88a07d9 | ||
|
|
588f8bb96a | ||
|
|
c93c0dd721 | ||
|
|
fc59c254fd | ||
|
|
2f8b6a150f | ||
|
|
db60ac5c17 | ||
|
|
e1f09853c5 | ||
|
|
24656713a5 | ||
|
|
7dd0269292 | ||
|
|
8b87cea7aa | ||
|
|
c7559a6946 | ||
|
|
945c6080ad | ||
|
|
44590965d1 | ||
|
|
7ab64d678f | ||
|
|
e406a76b62 | ||
|
|
e26f175a8f | ||
|
|
d4ab84745d | ||
|
|
32dbc3101e | ||
|
|
0a924eb718 | ||
|
|
a284327bfc | ||
|
|
2ea38d6ecc | ||
|
|
6a34bbfddd | ||
|
|
58323ada4b | ||
|
|
5fd723cb80 | ||
|
|
5c626e6957 | ||
|
|
5d949842eb | ||
|
|
b595c17d78 | ||
|
|
b84973ba2b | ||
|
|
61be49e7b2 | ||
|
|
8faf5659ee | ||
|
|
cc9267a646 | ||
|
|
55838bb032 | ||
|
|
67619ac5e8 | ||
|
|
952b342859 | ||
|
|
c7149c460d | ||
|
|
fd0613ea0e | ||
|
|
36d2dddc59 | ||
|
|
63c5b05584 | ||
|
|
4b235e5b87 | ||
|
|
6c51fffdaa | ||
|
|
5d6d638c85 | ||
|
|
90eb515167 | ||
|
|
17526711a2 | ||
|
|
cf0118e090 | ||
|
|
868d6fec42 | ||
|
|
851f5854bf | ||
|
|
eb5428c971 | ||
|
|
81188df7ef | ||
|
|
9fd365cc41 | ||
|
|
999df6e40f | ||
|
|
076d069568 | ||
|
|
2738648197 | ||
|
|
36013009a1 | ||
|
|
1b60233862 | ||
|
|
2cba10dd05 | ||
|
|
b3944127ea | ||
|
|
f1674378ca | ||
|
|
6f0191e1cf | ||
|
|
1848844be6 | ||
|
|
8b6362c749 | ||
|
|
d860d13361 | ||
|
|
4b077dbf4c | ||
|
|
40f73bbfe2 | ||
|
|
f455706d7c | ||
|
|
23e9672476 | ||
|
|
36f992f95f | ||
|
|
b2c6d526ab | ||
|
|
fe1e833677 | ||
|
|
8df1b9e8e5 | ||
|
|
38b0f71b01 | ||
|
|
29d2f115f8 | ||
|
|
0f677b4891 | ||
|
|
2f7dd04168 | ||
|
|
ed3b667985 | ||
|
|
6ae1d8c158 | ||
|
|
404bced97b | ||
|
|
5af49c8a82 | ||
|
|
85aa98e8e2 | ||
|
|
330d102f62 | ||
|
|
32b33a7910 | ||
|
|
17c6a0f28a | ||
|
|
7341eed1cf | ||
|
|
ff99fbfbc9 | ||
|
|
9f67fdc771 | ||
|
|
521143a16b | ||
|
|
2622a25b12 | ||
|
|
a91e925221 | ||
|
|
6c3289d5a5 | ||
|
|
988a91ac06 | ||
|
|
aa7c913e9a | ||
|
|
56db9feaa4 | ||
|
|
5ace0f13c9 | ||
|
|
076e6c9479 | ||
|
|
8277b1192e | ||
|
|
150b978b0e | ||
|
|
6c72096bfe | ||
|
|
87c18cea80 | ||
|
|
e658734084 | ||
|
|
ec4f350baa | ||
|
|
095f583211 | ||
|
|
3c864cf6d2 | ||
|
|
eb4b21ce9f | ||
|
|
ff5349fd90 | ||
|
|
1f34ffa85d | ||
|
|
e98cab1f7c | ||
|
|
aabc9659a2 | ||
|
|
8d8d308f7a | ||
|
|
3ebd4595c6 | ||
|
|
7e1168946f | ||
|
|
134689d8aa | ||
|
|
56282f9cbb | ||
|
|
b4713741b1 | ||
|
|
e42fe3bd61 | ||
|
|
4fd2dade60 | ||
|
|
e12b03504c | ||
|
|
153156c1fa | ||
|
|
3ecc69da2b | ||
|
|
07ad29da41 | ||
|
|
7d0de0b26f | ||
|
|
77fab9c78f | ||
|
|
3a8f3272c7 | ||
|
|
2d44cbac1b | ||
|
|
893d72677b | ||
|
|
979eca4066 | ||
|
|
258d13e746 | ||
|
|
779531da5d | ||
|
|
31d71006d7 | ||
|
|
64ca66c062 | ||
|
|
6e1a2b3427 | ||
|
|
f585235192 | ||
|
|
9355643554 | ||
|
|
ccc6055926 | ||
|
|
6639446bb8 | ||
|
|
e2925c585f | ||
|
|
6c76b0473c | ||
|
|
e1e19632a5 | ||
|
|
3e5364d5c0 | ||
|
|
6c98de4c8b | ||
|
|
9613dde4d2 | ||
|
|
d47df2e538 | ||
|
|
6fcacd5159 | ||
|
|
11b39cb020 | ||
|
|
d81f132db6 | ||
|
|
095697e789 | ||
|
|
62d98c3137 | ||
|
|
e80d5dc172 | ||
|
|
421e29db2d | ||
|
|
9e6e53583c | ||
|
|
3f59a7d84e | ||
|
|
21ffd788ab | ||
|
|
8dadfea724 | ||
|
|
00ce52ecf7 | ||
|
|
50ac13d3fd | ||
|
|
58318fec46 | ||
|
|
a49941113e | ||
|
|
595801cb99 | ||
|
|
0b469f09df | ||
|
|
1e1f4e4a47 | ||
|
|
c63e2ae7c8 | ||
|
|
d3d3fa990e | ||
|
|
21980b7e71 |
@@ -7,3 +7,4 @@
|
||||
^shiny\.cmd$
|
||||
^run\.R$
|
||||
^\.gitignore$
|
||||
^res$
|
||||
|
||||
13
DESCRIPTION
13
DESCRIPTION
@@ -1,10 +1,10 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 0.1.12
|
||||
Date: 2012-11-21
|
||||
Version: 0.4.1
|
||||
Date: 2013-01-23
|
||||
Author: RStudio, Inc.
|
||||
Maintainer: Joe Cheng <joe@rstudio.org>
|
||||
Maintainer: Winston Chang <winston@rstudio.com>
|
||||
Description: Shiny makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive pre-built widgets make it possible to build
|
||||
@@ -24,8 +24,10 @@ Imports:
|
||||
xtable,
|
||||
digest
|
||||
Suggests:
|
||||
markdown
|
||||
URL: https://github.com/rstudio/shiny, http://rstudio.github.com/shiny/tutorial
|
||||
markdown,
|
||||
Cairo,
|
||||
testthat
|
||||
URL: http://www.rstudio.com/shiny/
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Collate:
|
||||
'map.R'
|
||||
@@ -42,3 +44,4 @@ Collate:
|
||||
'shinyui.R'
|
||||
'slider.R'
|
||||
'bootstrap.R'
|
||||
'run-url.R'
|
||||
|
||||
51
NAMESPACE
51
NAMESPACE
@@ -1,3 +1,25 @@
|
||||
S3method("$",reactivevalues)
|
||||
S3method("$",shinyoutput)
|
||||
S3method("$<-",reactivevalues)
|
||||
S3method("$<-",shinyoutput)
|
||||
S3method("[",reactivevalues)
|
||||
S3method("[",shinyoutput)
|
||||
S3method("[<-",reactivevalues)
|
||||
S3method("[<-",shinyoutput)
|
||||
S3method("[[",reactivevalues)
|
||||
S3method("[[",shinyoutput)
|
||||
S3method("[[<-",reactivevalues)
|
||||
S3method("[[<-",shinyoutput)
|
||||
S3method("names<-",reactivevalues)
|
||||
S3method(as.character,shiny.tag)
|
||||
S3method(as.character,shiny.tag.list)
|
||||
S3method(as.list,reactivevalues)
|
||||
S3method(format,shiny.tag)
|
||||
S3method(format,shiny.tag.list)
|
||||
S3method(names,reactivevalues)
|
||||
S3method(print,shiny.tag)
|
||||
S3method(print,shiny.tag.list)
|
||||
export(HTML)
|
||||
export(a)
|
||||
export(addResourcePath)
|
||||
export(animationOptions)
|
||||
@@ -12,6 +34,7 @@ export(downloadButton)
|
||||
export(downloadHandler)
|
||||
export(downloadLink)
|
||||
export(em)
|
||||
export(exprToFunction)
|
||||
export(fileInput)
|
||||
export(h1)
|
||||
export(h2)
|
||||
@@ -21,16 +44,17 @@ export(h5)
|
||||
export(h6)
|
||||
export(headerPanel)
|
||||
export(helpText)
|
||||
export(HTML)
|
||||
export(htmlOutput)
|
||||
export(img)
|
||||
export(includeHTML)
|
||||
export(includeMarkdown)
|
||||
export(includeText)
|
||||
export(invalidateLater)
|
||||
export(isolate)
|
||||
export(mainPanel)
|
||||
export(numericInput)
|
||||
export(observe)
|
||||
export(outputOptions)
|
||||
export(p)
|
||||
export(pageWithSidebar)
|
||||
export(plotOutput)
|
||||
@@ -43,10 +67,19 @@ export(reactiveTable)
|
||||
export(reactiveText)
|
||||
export(reactiveTimer)
|
||||
export(reactiveUI)
|
||||
export(reactiveValues)
|
||||
export(reactiveValuesToList)
|
||||
export(renderPlot)
|
||||
export(renderPrint)
|
||||
export(renderTable)
|
||||
export(renderText)
|
||||
export(renderUI)
|
||||
export(repeatable)
|
||||
export(runApp)
|
||||
export(runExample)
|
||||
export(runGist)
|
||||
export(runGitHub)
|
||||
export(runUrl)
|
||||
export(selectInput)
|
||||
export(shinyServer)
|
||||
export(shinyUI)
|
||||
@@ -56,8 +89,8 @@ export(sliderInput)
|
||||
export(span)
|
||||
export(strong)
|
||||
export(submitButton)
|
||||
export(tableOutput)
|
||||
export(tabPanel)
|
||||
export(tableOutput)
|
||||
export(tabsetPanel)
|
||||
export(tag)
|
||||
export(tagAppendChild)
|
||||
@@ -68,20 +101,8 @@ export(textOutput)
|
||||
export(uiOutput)
|
||||
export(verbatimTextOutput)
|
||||
export(wellPanel)
|
||||
import(RJSONIO)
|
||||
import(caTools)
|
||||
import(digest)
|
||||
import(RJSONIO)
|
||||
import(websockets)
|
||||
import(xtable)
|
||||
S3method(as.character,shiny.tag)
|
||||
S3method(as.character,shiny.tag.list)
|
||||
S3method(as.list,reactvaluesreader)
|
||||
S3method(format,shiny.tag)
|
||||
S3method(format,shiny.tag.list)
|
||||
S3method(names,reactvaluesreader)
|
||||
S3method(print,shiny.tag)
|
||||
S3method(print,shiny.tag.list)
|
||||
S3method(reactive,default)
|
||||
S3method(reactive,"function")
|
||||
S3method("$",reactvaluesreader)
|
||||
S3method("$<-",shinyoutput)
|
||||
|
||||
120
NEWS
120
NEWS
@@ -1,3 +1,123 @@
|
||||
shiny 0.4.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix bug where width and height weren't passed along properly from
|
||||
`reactivePlot` to `renderPlot`.
|
||||
|
||||
* Fix bug where infinite recursion would happen when `reactivePlot` was passed
|
||||
a function for width or height.
|
||||
|
||||
shiny 0.4.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Added suspend/resume capability to observers.
|
||||
|
||||
* Output objects are automatically suspended when they are hidden on the user's
|
||||
web browser.
|
||||
|
||||
* `runGist()` accepts GitHub's new URL format, which includes the username.
|
||||
|
||||
* `reactive()` and `observe()` now take expressions instead of functions.
|
||||
|
||||
* `reactiveText()`, `reactivePlot()`, and so on, have been renamed to
|
||||
`renderText()`, `renderPlot()`, etc. They also now take expressions instead
|
||||
of functions.
|
||||
|
||||
* Fixed a bug where empty values in a numericInput were sent to the R process
|
||||
as 0. They are now sent as NA.
|
||||
|
||||
shiny 0.3.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix issue #91: bug where downloading files did not work.
|
||||
|
||||
* Add [[<- operator for shinyoutput object, making it possible to assign values
|
||||
with `output[['plot1']] <- ...`.
|
||||
|
||||
* Reactive functions now preserve the visible/invisible state of their returned
|
||||
values.
|
||||
|
||||
shiny 0.3.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Reactive functions are now evaluated lazily.
|
||||
|
||||
* Add `reactiveValues()`.
|
||||
|
||||
* Using `as.list()` to convert a reactivevalues object (like `input`) to a list
|
||||
is deprecated. The new function `reactiveValuesToList()` should be used
|
||||
instead.
|
||||
|
||||
* Add `isolate()`. This function is used for accessing reactive functions,
|
||||
without them invalidating their parent contexts.
|
||||
|
||||
* Fix issue #58: bug where reactive functions are not re-run when all items in
|
||||
a checkboxGroup are unchecked.
|
||||
|
||||
* Fix issue #71, where `reactiveTable()` would return blank if the first
|
||||
element of a data frame was NA.
|
||||
|
||||
* In `plotOutput`, better validation for CSS units when specifying width and
|
||||
height.
|
||||
|
||||
* `reactivePrint()` no longer displays invisible output.
|
||||
|
||||
* `reactiveText()` no longer displays printed output, only the return value
|
||||
from a function.
|
||||
|
||||
* The `runGitHub()` and `runUrl()` functions have been added, for running
|
||||
Shiny apps from GitHub repositories and zip/tar files at remote URLs.
|
||||
|
||||
* Fix issue #64, where pressing Enter in a textbox would cause a form to
|
||||
submit.
|
||||
|
||||
shiny 0.2.4
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* `runGist` has been updated to use the new download URLs from
|
||||
https://gist.github.com.
|
||||
|
||||
* Shiny now uses `CairoPNG()` for output, when the Cairo package is available.
|
||||
This provides better-looking output on Linux and Windows.
|
||||
|
||||
shiny 0.2.3
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Ignore request variables for routing purposes
|
||||
|
||||
shiny 0.2.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix CRAN warning (assigning to global environment)
|
||||
|
||||
|
||||
shiny 0.2.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* [BREAKING] Modify API of `downloadHandler`: The `content` function now takes
|
||||
a file path, not writable connection, as an argument. This makes it much
|
||||
easier to work with APIs that only write to file paths, not connections.
|
||||
|
||||
|
||||
shiny 0.2.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix subtle name resolution bug--the usual symptom being S4 methods not being
|
||||
invoked correctly when called from inside of ui.R or server.R
|
||||
|
||||
|
||||
shiny 0.1.14
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix slider animator, which broke in 0.1.10
|
||||
|
||||
|
||||
shiny 0.1.13
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix temp file leak in reactivePlot
|
||||
|
||||
|
||||
shiny 0.1.12
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -708,7 +708,7 @@ tabsetPanel <- function(..., id = NULL) {
|
||||
#' @param outputId output variable to read the value from
|
||||
#' @return A text output element that can be included in a panel
|
||||
#' @details Text is HTML-escaped prior to rendering. This element is often used
|
||||
#' to dispaly \link{reactiveText} output variables.
|
||||
#' to display \link{renderText} output variables.
|
||||
#' @examples
|
||||
#' h3(textOutput("caption"))
|
||||
#' @export
|
||||
@@ -723,7 +723,7 @@ textOutput <- function(outputId) {
|
||||
#' @param outputId output variable to read the value from
|
||||
#' @return A verbatim text output element that can be included in a panel
|
||||
#' @details Text is HTML-escaped prior to rendering. This element is often used
|
||||
#' with the \link{reactivePrint} function to preserve fixed-width formatting
|
||||
#' with the \link{renderPrint} function to preserve fixed-width formatting
|
||||
#' of printed objects.
|
||||
#' @examples
|
||||
#' mainPanel(
|
||||
@@ -740,9 +740,11 @@ verbatimTextOutput <- function(outputId) {
|
||||
|
||||
#' Create a plot output element
|
||||
#'
|
||||
#' Render a \link{reactivePlot} within an application page.
|
||||
#' Render a \link{renderPlot} within an application page.
|
||||
#' @param outputId output variable to read the plot from
|
||||
#' @param width Plot width
|
||||
#' @param width Plot width. Must be a valid CSS unit (like \code{"100\%"},
|
||||
#' \code{"400px"}, \code{"auto"}) or a number, which will be coerced to a
|
||||
#' string and have \code{"px"} appended.
|
||||
#' @param height Plot height
|
||||
#' @return A plot output element that can be included in a panel
|
||||
#' @examples
|
||||
@@ -752,13 +754,14 @@ verbatimTextOutput <- function(outputId) {
|
||||
#' )
|
||||
#' @export
|
||||
plotOutput <- function(outputId, width = "100%", height="400px") {
|
||||
style <- paste("width:", width, ";", "height:", height)
|
||||
style <- paste("width:", validateCssUnit(width), ";",
|
||||
"height:", validateCssUnit(height))
|
||||
div(id = outputId, class="shiny-plot-output", style = style)
|
||||
}
|
||||
|
||||
#' Create a table output element
|
||||
#'
|
||||
#' Render a \link{reactiveTable} within an application page.
|
||||
#' Render a \link{renderTable} within an application page.
|
||||
#' @param outputId output variable to read the table from
|
||||
#' @return A table output element that can be included in a panel
|
||||
#' @examples
|
||||
@@ -776,7 +779,7 @@ tableOutput <- function(outputId) {
|
||||
#' text will be included within an HTML \code{div} tag, and is presumed to
|
||||
#' contain HTML content which should not be escaped.
|
||||
#'
|
||||
#' \code{uiOutput} is intended to be used with \code{reactiveUI} on the
|
||||
#' \code{uiOutput} is intended to be used with \code{renderUI} on the
|
||||
#' server side. It is currently just an alias for \code{htmlOutput}.
|
||||
#'
|
||||
#' @param outputId output variable to read the value from
|
||||
@@ -841,4 +844,15 @@ downloadLink <- function(outputId, label="Download", class=NULL) {
|
||||
href='',
|
||||
target='_blank',
|
||||
label)
|
||||
}
|
||||
}
|
||||
|
||||
validateCssUnit <- function(x) {
|
||||
if (is.character(x) &&
|
||||
!grepl("^(auto|((\\.\\d+)|(\\d+(\\.\\d+)?))(%|in|cm|mm|em|ex|pt|pc|px))$", x)) {
|
||||
stop('"', x, '" is not a valid CSS unit (e.g., "100%", "400px", "auto")')
|
||||
}
|
||||
if (is.numeric(x)) {
|
||||
x <- paste(x, "px", sep = "")
|
||||
}
|
||||
x
|
||||
}
|
||||
|
||||
9
R/map.R
9
R/map.R
@@ -61,15 +61,6 @@ Map <- setRefClass(
|
||||
)
|
||||
)
|
||||
|
||||
`[.Map` <- function(map, name) {
|
||||
map$get(name)
|
||||
}
|
||||
|
||||
`[<-.Map` <- function(map, name, value) {
|
||||
map$set(name, value)
|
||||
return(map)
|
||||
}
|
||||
|
||||
as.list.Map <- function(map) {
|
||||
sapply(map$keys(),
|
||||
map$get,
|
||||
|
||||
82
R/react.R
82
R/react.R
@@ -2,38 +2,34 @@ Context <- setRefClass(
|
||||
'Context',
|
||||
fields = list(
|
||||
id = 'character',
|
||||
.label = 'character', # For debug purposes
|
||||
.invalidated = 'logical',
|
||||
.callbacks = 'list',
|
||||
.hintCallbacks = 'list'
|
||||
.invalidateCallbacks = 'list',
|
||||
.flushCallbacks = 'list'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
initialize = function(label='') {
|
||||
id <<- .getReactiveEnvironment()$nextId()
|
||||
.invalidated <<- FALSE
|
||||
.callbacks <<- list()
|
||||
.hintCallbacks <<- list()
|
||||
.invalidateCallbacks <<- list()
|
||||
.flushCallbacks <<- list()
|
||||
.label <<- label
|
||||
},
|
||||
run = function(func) {
|
||||
"Run the provided function under this context."
|
||||
env <- .getReactiveEnvironment()
|
||||
env$runWith(.self, func)
|
||||
},
|
||||
invalidateHint = function() {
|
||||
"Let this context know it may or may not be invalidated very soon; that
|
||||
is, something in its dependency graph has been invalidated but there's no
|
||||
guarantee that the cascade of invalidations will reach all the way here.
|
||||
This is used to show progress in the UI."
|
||||
lapply(.hintCallbacks, function(func) {
|
||||
func()
|
||||
})
|
||||
},
|
||||
invalidate = function() {
|
||||
"Schedule this context for invalidation. It will not actually be
|
||||
invalidated until the next call to \\code{\\link{flushReact}}."
|
||||
"Invalidate this context. It will immediately call the callbacks
|
||||
that have been registered with onInvalidate()."
|
||||
if (.invalidated)
|
||||
return()
|
||||
.invalidated <<- TRUE
|
||||
.getReactiveEnvironment()$addPendingInvalidate(.self)
|
||||
|
||||
lapply(.invalidateCallbacks, function(func) {
|
||||
func()
|
||||
})
|
||||
NULL
|
||||
},
|
||||
onInvalidate = function(func) {
|
||||
@@ -43,15 +39,21 @@ Context <- setRefClass(
|
||||
if (.invalidated)
|
||||
func()
|
||||
else
|
||||
.callbacks <<- c(.callbacks, func)
|
||||
.invalidateCallbacks <<- c(.invalidateCallbacks, func)
|
||||
NULL
|
||||
},
|
||||
onInvalidateHint = function(func) {
|
||||
.hintCallbacks <<- c(.hintCallbacks, func)
|
||||
addPendingFlush = function() {
|
||||
"Tell the reactive environment that this context should be flushed the
|
||||
next time flushReact() called."
|
||||
.getReactiveEnvironment()$addPendingFlush(.self)
|
||||
},
|
||||
executeCallbacks = function() {
|
||||
onFlush = function(func) {
|
||||
"Register a function to be called when this context is flushed."
|
||||
.flushCallbacks <<- c(.flushCallbacks, func)
|
||||
},
|
||||
executeFlushCallbacks = function() {
|
||||
"For internal use only."
|
||||
lapply(.callbacks, function(func) {
|
||||
lapply(.flushCallbacks, function(func) {
|
||||
withCallingHandlers({
|
||||
func()
|
||||
}, warning = function(e) {
|
||||
@@ -66,12 +68,18 @@ Context <- setRefClass(
|
||||
|
||||
ReactiveEnvironment <- setRefClass(
|
||||
'ReactiveEnvironment',
|
||||
fields = c('.currentContext', '.nextId', '.pendingInvalidate'),
|
||||
fields = list(
|
||||
.currentContext = 'ANY',
|
||||
.nextId = 'integer',
|
||||
.pendingFlush = 'list',
|
||||
.inFlush = 'logical'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
.currentContext <<- NULL
|
||||
.nextId <<- 0L
|
||||
.pendingInvalidate <<- list()
|
||||
.pendingFlush <<- list()
|
||||
.inFlush <<- FALSE
|
||||
},
|
||||
nextId = function() {
|
||||
.nextId <<- .nextId + 1L
|
||||
@@ -90,27 +98,27 @@ ReactiveEnvironment <- setRefClass(
|
||||
on.exit(.currentContext <<- old.ctx)
|
||||
func()
|
||||
},
|
||||
addPendingInvalidate = function(ctx) {
|
||||
.pendingInvalidate <<- c(.pendingInvalidate, ctx)
|
||||
addPendingFlush = function(ctx) {
|
||||
.pendingFlush <<- c(ctx, .pendingFlush)
|
||||
},
|
||||
flush = function() {
|
||||
while (length(.pendingInvalidate) > 0) {
|
||||
contexts <- .pendingInvalidate
|
||||
.pendingInvalidate <<- list()
|
||||
lapply(contexts, function(ctx) {
|
||||
ctx$executeCallbacks()
|
||||
NULL
|
||||
})
|
||||
# If already in a flush, don't start another one
|
||||
if (.inFlush) return()
|
||||
.inFlush <<- TRUE
|
||||
on.exit(.inFlush <<- FALSE)
|
||||
|
||||
while (length(.pendingFlush) > 0) {
|
||||
ctx <- .pendingFlush[[1]]
|
||||
.pendingFlush <<- .pendingFlush[-1]
|
||||
ctx$executeFlushCallbacks()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
.reactiveEnvironment <- ReactiveEnvironment$new()
|
||||
.getReactiveEnvironment <- function() {
|
||||
if (!exists('.ReactiveEnvironment', envir=.GlobalEnv, inherits=FALSE)) {
|
||||
assign('.ReactiveEnvironment', ReactiveEnvironment$new(), envir=.GlobalEnv)
|
||||
}
|
||||
get('.ReactiveEnvironment', envir=.GlobalEnv, inherits=FALSE)
|
||||
.reactiveEnvironment
|
||||
}
|
||||
|
||||
# Causes any pending invalidations to run.
|
||||
|
||||
586
R/reactives.R
586
R/reactives.R
@@ -1,61 +1,57 @@
|
||||
Dependencies <- setRefClass(
|
||||
'Dependencies',
|
||||
Dependents <- setRefClass(
|
||||
'Dependents',
|
||||
fields = list(
|
||||
.dependencies = 'Map'
|
||||
.dependents = 'Map'
|
||||
),
|
||||
methods = list(
|
||||
register = function() {
|
||||
ctx <- .getReactiveEnvironment()$currentContext()
|
||||
if (!.dependencies$containsKey(ctx$id)) {
|
||||
.dependencies$set(ctx$id, ctx)
|
||||
if (!.dependents$containsKey(ctx$id)) {
|
||||
.dependents$set(ctx$id, ctx)
|
||||
ctx$onInvalidate(function() {
|
||||
.dependencies$remove(ctx$id)
|
||||
.dependents$remove(ctx$id)
|
||||
})
|
||||
}
|
||||
},
|
||||
invalidate = function() {
|
||||
lapply(
|
||||
.dependencies$values(),
|
||||
.dependents$values(),
|
||||
function(ctx) {
|
||||
ctx$invalidateHint()
|
||||
ctx$invalidate()
|
||||
NULL
|
||||
}
|
||||
)
|
||||
},
|
||||
invalidateHint = function() {
|
||||
lapply(
|
||||
.dependencies$values(),
|
||||
function(dep.ctx) {
|
||||
dep.ctx$invalidateHint()
|
||||
NULL
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
Values <- setRefClass(
|
||||
'Values',
|
||||
|
||||
# ReactiveValues ------------------------------------------------------------
|
||||
|
||||
ReactiveValues <- setRefClass(
|
||||
'ReactiveValues',
|
||||
fields = list(
|
||||
.values = 'environment',
|
||||
.dependencies = 'environment',
|
||||
# Dependencies for the list of names
|
||||
.namesDeps = 'Dependencies',
|
||||
# Dependencies for all values
|
||||
.allDeps = 'Dependencies'
|
||||
.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())
|
||||
.dependencies <<- 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=.dependencies, inherits=FALSE)) {
|
||||
assign(dep.key, ctx, pos=.dependencies, inherits=FALSE)
|
||||
if (!exists(dep.key, where=.dependents, inherits=FALSE)) {
|
||||
assign(dep.key, ctx, pos=.dependents, inherits=FALSE)
|
||||
ctx$onInvalidate(function() {
|
||||
rm(list=dep.key, pos=.dependencies, inherits=FALSE)
|
||||
rm(list=dep.key, pos=.dependents, inherits=FALSE)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,6 +61,8 @@ Values <- setRefClass(
|
||||
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())
|
||||
@@ -73,18 +71,21 @@ Values <- setRefClass(
|
||||
else {
|
||||
.namesDeps$invalidate()
|
||||
}
|
||||
.allDeps$invalidate()
|
||||
|
||||
if (hidden)
|
||||
.allValuesDeps$invalidate()
|
||||
else
|
||||
.valuesDeps$invalidate()
|
||||
|
||||
assign(key, value, pos=.values, inherits=FALSE)
|
||||
dep.keys <- objects(
|
||||
pos=.dependencies,
|
||||
pos=.dependents,
|
||||
pattern=paste('^\\Q', key, ':', '\\E', '\\d+$', sep=''),
|
||||
all.names=TRUE
|
||||
)
|
||||
lapply(
|
||||
mget(dep.keys, envir=.dependencies),
|
||||
mget(dep.keys, envir=.dependents),
|
||||
function(ctx) {
|
||||
ctx$invalidateHint()
|
||||
ctx$invalidate()
|
||||
NULL
|
||||
}
|
||||
@@ -101,198 +102,433 @@ Values <- setRefClass(
|
||||
.namesDeps$register()
|
||||
return(ls(.values, all.names=TRUE))
|
||||
},
|
||||
toList = function() {
|
||||
.allDeps$register()
|
||||
return(as.list(.values))
|
||||
toList = function(all.names=FALSE) {
|
||||
if (all.names)
|
||||
.allValuesDeps$register()
|
||||
|
||||
.valuesDeps$register()
|
||||
|
||||
return(as.list(.values, all.names=all.names))
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
`[.Values` <- function(values, name) {
|
||||
values$get(name)
|
||||
|
||||
# reactivevalues ------------------------------------------------------------
|
||||
# S3 wrapper class for ReactiveValues reference 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 expression 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
|
||||
}
|
||||
|
||||
`[<-.Values` <- function(values, name, value) {
|
||||
values$set(name, value)
|
||||
return(values)
|
||||
# Create a reactivevalues object
|
||||
#
|
||||
# @param values A ReactiveValues object
|
||||
# @param readonly Should this object be read-only?
|
||||
.createReactiveValues <- function(values = NULL, readonly = FALSE) {
|
||||
structure(list(impl=values), class='reactivevalues', readonly=readonly)
|
||||
}
|
||||
|
||||
.createValuesReader <- function(values) {
|
||||
acc <- list(impl=values)
|
||||
class(acc) <- 'reactvaluesreader'
|
||||
return(acc)
|
||||
#' @S3method $ reactivevalues
|
||||
`$.reactivevalues` <- function(x, name) {
|
||||
.subset2(x, 'impl')$get(name)
|
||||
}
|
||||
|
||||
#' @S3method $ reactvaluesreader
|
||||
`$.reactvaluesreader` <- function(x, name) {
|
||||
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 names reactvaluesreader
|
||||
names.reactvaluesreader <- function(x) {
|
||||
x[['impl']]$names()
|
||||
#' @S3method [[<- reactivevalues
|
||||
`[[<-.reactivevalues` <- `$<-.reactivevalues`
|
||||
|
||||
#' @S3method [ reactivevalues
|
||||
`[.reactivevalues` <- function(values, name) {
|
||||
stop("Single-bracket indexing of reactivevalues object is not allowed.")
|
||||
}
|
||||
|
||||
#' @S3method as.list reactvaluesreader
|
||||
as.list.reactvaluesreader <- function(x, ...) {
|
||||
x[['impl']]$toList()
|
||||
#' @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, ...) {
|
||||
shinyDeprecated("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 ----------------------------------------------------------------
|
||||
|
||||
Observable <- setRefClass(
|
||||
'Observable',
|
||||
fields = list(
|
||||
.func = 'function',
|
||||
.dependencies = 'Dependencies',
|
||||
.initialized = 'logical',
|
||||
.value = 'ANY'
|
||||
.label = 'character',
|
||||
.dependents = 'Dependents',
|
||||
.invalidated = 'logical',
|
||||
.running = 'logical',
|
||||
.value = 'ANY',
|
||||
.visible = 'logical',
|
||||
.execCount = 'integer'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function(func) {
|
||||
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 ",
|
||||
stop("Can't make a reactive expression from a function that takes one ",
|
||||
"or more parameters; only functions without parameters can be ",
|
||||
"reactive.")
|
||||
.func <<- func
|
||||
.initialized <<- FALSE
|
||||
.invalidated <<- TRUE
|
||||
.running <<- FALSE
|
||||
.label <<- label
|
||||
.execCount <<- 0L
|
||||
},
|
||||
getValue = function() {
|
||||
if (!.initialized) {
|
||||
.initialized <<- TRUE
|
||||
.dependents$register()
|
||||
|
||||
if (.invalidated || .running) {
|
||||
.self$.updateValue()
|
||||
}
|
||||
|
||||
.dependencies$register()
|
||||
|
||||
if (identical(class(.value), 'try-error'))
|
||||
stop(attr(.value, 'condition'))
|
||||
return(.value)
|
||||
|
||||
if (.visible)
|
||||
.value
|
||||
else
|
||||
invisible(.value)
|
||||
},
|
||||
.updateValue = function() {
|
||||
old.value <- .value
|
||||
|
||||
ctx <- Context$new()
|
||||
ctx <- Context$new(.label)
|
||||
ctx$onInvalidate(function() {
|
||||
.self$.updateValue()
|
||||
})
|
||||
ctx$onInvalidateHint(function() {
|
||||
.dependencies$invalidateHint()
|
||||
.invalidated <<- TRUE
|
||||
.dependents$invalidate()
|
||||
})
|
||||
.execCount <<- .execCount + 1L
|
||||
|
||||
.invalidated <<- FALSE
|
||||
|
||||
wasRunning <- .running
|
||||
.running <<- TRUE
|
||||
on.exit(.running <<- wasRunning)
|
||||
|
||||
ctx$run(function() {
|
||||
.value <<- try(.func(), silent=FALSE)
|
||||
result <- withVisible(try(.func(), silent=FALSE))
|
||||
.visible <<- result$visible
|
||||
.value <<- result$value
|
||||
})
|
||||
if (!identical(old.value, .value)) {
|
||||
.dependencies$invalidate()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#' 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
|
||||
#' Create a reactive expression
|
||||
#'
|
||||
#' Wraps a normal expression to create a reactive expression. Conceptually, a
|
||||
#' reactive expression is a expression whose result will change over time.
|
||||
#'
|
||||
#' Reactive expressions are expressions that can read reactive values and call other
|
||||
#' reactive expressions. Whenever a reactive value changes, any reactive expressions
|
||||
#' that depended on it are marked as "invalidated" and will automatically
|
||||
#' re-execute if necessary. If a reactive expression is marked as invalidated, any
|
||||
#' other reactive expressions that recently called it are also marked as
|
||||
#' invalidated. In this way, invalidations ripple through the expressions 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.)
|
||||
#'
|
||||
#'
|
||||
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{Shiny tutorial} for
|
||||
#' more information about reactive expressions.
|
||||
#'
|
||||
#' @param x An expression (quoted or unquoted).
|
||||
#' @param env The parent environment for the reactive expression. By default, this
|
||||
#' is the calling environment, the same as when defining an ordinary
|
||||
#' non-reactive expression.
|
||||
#' @param quoted Is the expression quoted? By default, this is \code{FALSE}.
|
||||
#' This is useful when you want to use an expression that is stored in a
|
||||
#' variable; to do so, it must be quoted with `quote()`.
|
||||
#' @param label A label for the reactive expression, useful for debugging.
|
||||
#'
|
||||
#' @examples
|
||||
#' values <- reactiveValues(A=1)
|
||||
#'
|
||||
#' reactiveB <- reactive({
|
||||
#' values$A + 1
|
||||
#' })
|
||||
#'
|
||||
#' # Can use quoted expressions
|
||||
#' reactiveC <- reactive(quote({ values$A + 2 }), quoted = TRUE)
|
||||
#'
|
||||
#' # To store expressions for later conversion to reactive, use quote()
|
||||
#' expr_q <- quote({ values$A + 3 })
|
||||
#' reactiveD <- reactive(expr_q, quoted = TRUE)
|
||||
#'
|
||||
#' # View the values from the R console with isolate()
|
||||
#' isolate(reactiveB())
|
||||
#' isolate(reactiveC())
|
||||
#' isolate(reactiveD())
|
||||
#'
|
||||
#' @export
|
||||
reactive <- function(x) {
|
||||
UseMethod("reactive")
|
||||
reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL) {
|
||||
fun <- exprToFunction(x, env, quoted)
|
||||
if (is.null(label))
|
||||
label <- deparse(body(fun))
|
||||
|
||||
Observable$new(fun, label)$getValue
|
||||
}
|
||||
#' @S3method reactive function
|
||||
reactive.function <- function(x) {
|
||||
return(Observable$new(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 expression 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 ------------------------------------------------------------------
|
||||
|
||||
Observer <- setRefClass(
|
||||
'Observer',
|
||||
fields = list(
|
||||
.func = 'function',
|
||||
.hintCallbacks = 'list'
|
||||
.label = 'character',
|
||||
.invalidateCallbacks = 'list',
|
||||
.execCount = 'integer',
|
||||
.onResume = 'function',
|
||||
.suspended = 'logical'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function(func) {
|
||||
initialize = function(func, label, suspended = FALSE) {
|
||||
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
|
||||
.suspended <<- suspended
|
||||
.onResume <<- function() NULL
|
||||
|
||||
# Defer the first running of this until flushReact is called
|
||||
ctx <- Context$new()
|
||||
ctx$onInvalidate(function() {
|
||||
run()
|
||||
})
|
||||
ctx$invalidate()
|
||||
.createContext()$invalidate()
|
||||
},
|
||||
run = function() {
|
||||
ctx <- Context$new()
|
||||
.createContext = function() {
|
||||
ctx <- Context$new(.label)
|
||||
|
||||
ctx$onInvalidate(function() {
|
||||
run()
|
||||
})
|
||||
ctx$onInvalidateHint(function() {
|
||||
lapply(.hintCallbacks, function(func) {
|
||||
lapply(.invalidateCallbacks, function(func) {
|
||||
func()
|
||||
NULL
|
||||
})
|
||||
|
||||
continue <- function() {
|
||||
ctx$addPendingFlush()
|
||||
}
|
||||
|
||||
if (.suspended == FALSE)
|
||||
continue()
|
||||
else
|
||||
.onResume <<- continue
|
||||
})
|
||||
|
||||
ctx$onFlush(function() {
|
||||
run()
|
||||
})
|
||||
|
||||
return(ctx)
|
||||
},
|
||||
run = function() {
|
||||
ctx <- .createContext()
|
||||
.execCount <<- .execCount + 1L
|
||||
ctx$run(.func)
|
||||
},
|
||||
onInvalidateHint = function(func) {
|
||||
.hintCallbacks <<- c(.hintCallbacks, func)
|
||||
onInvalidate = function(func) {
|
||||
"Register a function to run when this observer is invalidated"
|
||||
.invalidateCallbacks <<- c(.invalidateCallbacks, func)
|
||||
},
|
||||
suspend = function() {
|
||||
"Causes this observer to stop scheduling flushes (re-executions) in
|
||||
response to invalidations. If the observer was invalidated prior to this
|
||||
call but it has not re-executed yet (because it waits until onFlush is
|
||||
called) then that re-execution will still occur, becasue the flush is
|
||||
already scheduled."
|
||||
.suspended <<- TRUE
|
||||
},
|
||||
resume = function() {
|
||||
"Causes this observer to start re-executing in response to invalidations.
|
||||
If the observer was invalidated while suspended, then it will schedule
|
||||
itself for re-execution (pending flush)."
|
||||
if (.suspended) {
|
||||
.suspended <<- FALSE
|
||||
.onResume()
|
||||
.onResume <<- function() NULL
|
||||
}
|
||||
invisible()
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#' 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
|
||||
#' Creates an observer from the given expression An observer is like a reactive
|
||||
#' expression in that it can read reactive values and call reactive expressions, 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
|
||||
#' reactive expression, it doesn't yield a result and can't be used as an input
|
||||
#' to other reactive expressions. 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
|
||||
#' Another contrast between reactive expressions and observers is their execution
|
||||
#' strategy. Reactive expressions 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.
|
||||
#'
|
||||
#' @param x An expression (quoted or unquoted). Any return value will be ignored.
|
||||
#' @param env The parent environment for the reactive expression. By default, this
|
||||
#' is the calling environment, the same as when defining an ordinary
|
||||
#' non-reactive expression.
|
||||
#' @param quoted Is the expression quoted? By default, this is \code{FALSE}.
|
||||
#' This is useful when you want to use an expression that is stored in a
|
||||
#' variable; to do so, it must be quoted with `quote()`.
|
||||
#' @param label A label for the observer, useful for debugging.
|
||||
#' @param suspended If \code{TRUE}, start the observer in a suspended state.
|
||||
#' If \code{FALSE} (the default), start in a non-suspended state.
|
||||
#'
|
||||
#' @examples
|
||||
#' values <- reactiveValues(A=1)
|
||||
#'
|
||||
#' obsB <- observe({
|
||||
#' print(values$A + 1)
|
||||
#' })
|
||||
#'
|
||||
#' # Can use quoted expressions
|
||||
#' obsC <- observe(quote({ print(values$A + 2) }), quoted = TRUE)
|
||||
#'
|
||||
#' # To store expressions for later conversion to observe, use quote()
|
||||
#' expr_q <- quote({ print(values$A + 3) })
|
||||
#' obsD <- observe(expr_q, quoted = TRUE)
|
||||
#'
|
||||
#' # In a normal Shiny app, the web client will trigger flush events. If you
|
||||
#' # are at the console, you can force a flush with flushReact()
|
||||
#' shiny:::flushReact()
|
||||
#'
|
||||
#' @export
|
||||
observe <- function(func) {
|
||||
Observer$new(func)
|
||||
invisible()
|
||||
observe <- function(x, env=parent.frame(), quoted=FALSE, label=NULL,
|
||||
suspended=FALSE) {
|
||||
|
||||
fun <- exprToFunction(x, env, quoted)
|
||||
if (is.null(label))
|
||||
label <- deparse(body(fun))
|
||||
|
||||
invisible(Observer$new(fun, label=label, suspended=suspended))
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
#' 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
|
||||
#' \link[=reactive]{Reactive expressions} 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.
|
||||
@@ -307,11 +543,11 @@ observe <- function(func) {
|
||||
#' @seealso invalidateLater
|
||||
#' @export
|
||||
reactiveTimer <- function(intervalMs=1000) {
|
||||
dependencies <- Map$new()
|
||||
dependents <- Map$new()
|
||||
timerCallbacks$schedule(intervalMs, function() {
|
||||
timerCallbacks$schedule(intervalMs, sys.function())
|
||||
lapply(
|
||||
dependencies$values(),
|
||||
dependents$values(),
|
||||
function(dep.ctx) {
|
||||
dep.ctx$invalidate()
|
||||
NULL
|
||||
@@ -319,10 +555,10 @@ reactiveTimer <- function(intervalMs=1000) {
|
||||
})
|
||||
return(function() {
|
||||
ctx <- .getReactiveEnvironment()$currentContext()
|
||||
if (!dependencies$containsKey(ctx$id)) {
|
||||
dependencies$set(ctx$id, ctx)
|
||||
if (!dependents$containsKey(ctx$id)) {
|
||||
dependents$set(ctx$id, ctx)
|
||||
ctx$onInvalidate(function() {
|
||||
dependencies$remove(ctx$id)
|
||||
dependents$remove(ctx$id)
|
||||
})
|
||||
}
|
||||
return(Sys.time())
|
||||
@@ -343,3 +579,81 @@ invalidateLater <- function(millis) {
|
||||
})
|
||||
invisible()
|
||||
}
|
||||
|
||||
#' Create a non-reactive scope for an expression
|
||||
#'
|
||||
#' Executes the given expression in a scope where reactive values or expression
|
||||
#' 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 expression's value.) The \code{isolate}
|
||||
#' function lets you read a reactive value or expression without establishing this
|
||||
#' relationship.
|
||||
#'
|
||||
#' The expression given to \code{isolate()} is evaluated in the calling
|
||||
#' environment. This means that if you assign a variable inside the
|
||||
#' \code{isolate()}, its value will be visible outside of the \code{isolate()}.
|
||||
#' If you want to avoid this, you can use \code{\link{local}()} inside the
|
||||
#' \code{isolate()}.
|
||||
#'
|
||||
#' This function can also be useful for calling reactive expression at the
|
||||
#' console, which can be useful for debugging. To do so, simply wrap the
|
||||
#' calls to the reactive expression with \code{isolate()}.
|
||||
#'
|
||||
#' @param expr An expression that can access reactive values or expressions.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' observe({
|
||||
#' 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)
|
||||
#' })
|
||||
#'
|
||||
#' observe({
|
||||
#' 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)
|
||||
#' })
|
||||
#'
|
||||
#' observe({
|
||||
#' x <- 1
|
||||
#' # x outside of isolate() is affected
|
||||
#' isolate(x <- 2)
|
||||
#' print(x) # 2
|
||||
#'
|
||||
#' y <- 1
|
||||
#' # Use local() to avoid affecting calling environment
|
||||
#' isolate(local(y <- 2))
|
||||
#' print(y) # 1
|
||||
#' })
|
||||
#'
|
||||
#' }
|
||||
#'
|
||||
#' # Can also use isolate to call reactive expressions from the R console
|
||||
#' values <- reactiveValues(A=1)
|
||||
#' fun <- reactive({ as.character(values$A) })
|
||||
#' isolate(fun())
|
||||
#' # "1"
|
||||
#'
|
||||
#' # isolate also works if the reactive expression accesses values from the
|
||||
#' # input object, like input$x
|
||||
#'
|
||||
#' @export
|
||||
isolate <- function(expr) {
|
||||
ctx <- Context$new('[isolate]')
|
||||
ctx$run(function() {
|
||||
expr
|
||||
})
|
||||
}
|
||||
|
||||
164
R/run-url.R
Normal file
164
R/run-url.R
Normal file
@@ -0,0 +1,164 @@
|
||||
#' Run a Shiny application from https://gist.github.com
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted on GitHub as a gist.
|
||||
#'
|
||||
#' @param gist The identifier of the gist. For example, if the gist is
|
||||
#' https://gist.github.com/jcheng5/3239667, then \code{3239667},
|
||||
#' \code{'3239667'}, and \code{'https://gist.github.com/jcheng5/3239667'}
|
||||
#' are all valid values.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' port 8100.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGist(3239667)
|
||||
#' runGist("https://gist.github.com/jcheng5/3239667")
|
||||
#'
|
||||
#' # Old URL format without username
|
||||
#' runGist("https://gist.github.com/3239667")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runGist <- function(gist,
|
||||
port=8100L,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive())) {
|
||||
|
||||
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
|
||||
sprintf('https://gist.github.com/%s/download', gist)
|
||||
} else if(grepl('^https://gist.github.com/([^/]+/)?([0-9a-f]+)$', gist)) {
|
||||
paste(gist, '/download', sep='')
|
||||
} else {
|
||||
stop('Unrecognized gist identifier format')
|
||||
}
|
||||
|
||||
runUrl(gistUrl, filetype=".tar.gz", subdir=NULL, port=port,
|
||||
launch.browser=launch.browser)
|
||||
}
|
||||
|
||||
|
||||
#' Run a Shiny application from a GitHub repository
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted in a GitHub repository.
|
||||
#'
|
||||
#' @param repo Name of the repository
|
||||
#' @param username GitHub username
|
||||
#' @param ref Desired git reference. Could be a commit, tag, or branch
|
||||
#' name. Defaults to \code{"master"}.
|
||||
#' @param subdir A subdirectory in the repository that contains the app. By
|
||||
#' default, this function will run an app from the top level of the repo, but
|
||||
#' you can use a path such as `\code{"inst/shinyapp"}.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' port 8100.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runGitHub("shiny_example", "rstudio")
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the repo
|
||||
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runGitHub <- function(repo, username = getOption("github.user"),
|
||||
ref = "master", subdir = NULL, port = 8100,
|
||||
launch.browser = getOption('shiny.launch.browser', interactive())) {
|
||||
|
||||
if (is.null(ref)) {
|
||||
stop("Must specify either a ref. ")
|
||||
}
|
||||
|
||||
message("Downloading github repo(s) ",
|
||||
paste(repo, ref, sep = "/", collapse = ", "),
|
||||
" from ",
|
||||
paste(username, collapse = ", "))
|
||||
name <- paste(username, "-", repo, sep = "")
|
||||
|
||||
url <- paste("https://github.com/", username, "/", repo, "/archive/",
|
||||
ref, ".tar.gz", sep = "")
|
||||
|
||||
runUrl(url, subdir=subdir, port=port, launch.browser=launch.browser)
|
||||
}
|
||||
|
||||
|
||||
#' Run a Shiny application from a URL
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted at a downloadable
|
||||
#' URL. The Shiny application must be saved in a .zip, .tar, or .tar.gz file.
|
||||
#' The Shiny application files must be contained in a subdirectory in the
|
||||
#' archive. For example, the files might be \code{myapp/server.r} and
|
||||
#' \code{myapp/ui.r}.
|
||||
#'
|
||||
#' @param url URL of the application.
|
||||
#' @param filetype The file type (\code{".zip"}, \code{".tar"}, or
|
||||
#' \code{".tar.gz"}. Defaults to the file extension taken from the url.
|
||||
#' @param subdir A subdirectory in the repository that contains the app. By
|
||||
#' default, this function will run an app from the top level of the repo, but
|
||||
#' you can use a path such as `\code{"inst/shinyapp"}.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' port 8100.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the archive
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
#' subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, port = 8100,
|
||||
launch.browser = getOption('shiny.launch.browser', interactive())) {
|
||||
|
||||
if (!is.null(subdir) && ".." %in% strsplit(subdir, '/')[[1]])
|
||||
stop("'..' not allowed in subdir")
|
||||
|
||||
if (is.null(filetype))
|
||||
filetype <- basename(url)
|
||||
|
||||
if (grepl("\\.tar\\.gz$", filetype))
|
||||
fileext <- ".tar.gz"
|
||||
else if (grepl("\\.tar$", filetype))
|
||||
fileext <- ".tar"
|
||||
else if (grepl("\\.zip$", filetype))
|
||||
fileext <- ".zip"
|
||||
else
|
||||
stop("Unknown file extension.")
|
||||
|
||||
message("Downloading ", url)
|
||||
filePath <- tempfile('shinyapp', fileext=fileext)
|
||||
if (download(url, filePath, mode = "wb", quiet = TRUE) != 0)
|
||||
stop("Failed to download URL ", url)
|
||||
on.exit(unlink(filePath))
|
||||
|
||||
if (fileext %in% c(".tar", ".tar.gz")) {
|
||||
# Regular untar commonly causes two problems on Windows with github tarballs:
|
||||
# 1) If RTools' tar.exe is in the path, you get cygwin path warnings which
|
||||
# throw list=TRUE off;
|
||||
# 2) If the internal untar implementation is used, it chokes on the 'g'
|
||||
# type flag that github uses (to stash their commit hash info).
|
||||
# By using our own forked/modified untar2 we sidestep both issues.
|
||||
dirname <- untar2(filePath, list=TRUE)[1]
|
||||
untar2(filePath, exdir = dirname(filePath))
|
||||
|
||||
} else if (fileext == ".zip") {
|
||||
dirname <- as.character(unzip(filePath, list=TRUE)$Name[1])
|
||||
unzip(filePath, exdir = dirname(filePath))
|
||||
}
|
||||
|
||||
appdir <- file.path(dirname(filePath), dirname)
|
||||
on.exit(unlink(appdir, recursive = TRUE), add = TRUE)
|
||||
|
||||
appsubdir <- ifelse(is.null(subdir), appdir, file.path(appdir, subdir))
|
||||
runApp(appsubdir, port=port, launch.browser=launch.browser)
|
||||
}
|
||||
269
R/shiny.R
269
R/shiny.R
@@ -18,9 +18,11 @@ ShinyApp <- setRefClass(
|
||||
.websocket = 'list',
|
||||
.invalidatedOutputValues = 'Map',
|
||||
.invalidatedOutputErrors = 'Map',
|
||||
.outputs = 'list', # Keeps track of all the output observer objects
|
||||
.outputOptions = 'list', # Options for each of the output observer objects
|
||||
.progressKeys = 'character',
|
||||
.fileUploadContext = 'FileUploadContext',
|
||||
session = 'Values',
|
||||
session = 'ReactiveValues',
|
||||
token = 'character', # Used to identify this instance in URLs
|
||||
plots = 'Map',
|
||||
downloads = 'Map',
|
||||
@@ -34,13 +36,15 @@ ShinyApp <- setRefClass(
|
||||
.progressKeys <<- character(0)
|
||||
# TODO: Put file upload context in user/app-specific dir if possible
|
||||
.fileUploadContext <<- FileUploadContext$new()
|
||||
session <<- Values$new()
|
||||
session <<- ReactiveValues$new()
|
||||
|
||||
token <<- createUniqueId(16)
|
||||
.outputs <<- list()
|
||||
.outputOptions <<- list()
|
||||
|
||||
allowDataUriScheme <<- TRUE
|
||||
},
|
||||
defineOutput = function(name, func) {
|
||||
defineOutput = function(name, func, label) {
|
||||
"Binds an output generating function to this name. The function can either
|
||||
take no parameters, or have named parameters for \\code{name} and
|
||||
\\code{shinyapp} (in the future this list may expand, so it is a good idea
|
||||
@@ -49,6 +53,11 @@ ShinyApp <- setRefClass(
|
||||
# jcheng 08/31/2012: User submitted an example of a dynamically calculated
|
||||
# name not working unless name was eagerly evaluated. Yikes!
|
||||
force(name)
|
||||
|
||||
# If overwriting an output object, suspend the previous copy of it
|
||||
if (!is.null(.outputs[[name]])) {
|
||||
.outputs[[name]]$suspend()
|
||||
}
|
||||
|
||||
if (is.function(func)) {
|
||||
if (length(formals(func)) != 0) {
|
||||
@@ -58,7 +67,7 @@ ShinyApp <- setRefClass(
|
||||
}
|
||||
}
|
||||
|
||||
obs <- Observer$new(function() {
|
||||
obs <- observe({
|
||||
|
||||
value <- try(func(), silent=FALSE)
|
||||
|
||||
@@ -74,11 +83,15 @@ ShinyApp <- setRefClass(
|
||||
}
|
||||
else
|
||||
.invalidatedOutputValues$set(name, value)
|
||||
})
|
||||
}, label=label, suspended=TRUE)
|
||||
|
||||
obs$onInvalidateHint(function() {
|
||||
obs$onInvalidate(function() {
|
||||
showProgress(name)
|
||||
})
|
||||
|
||||
.outputs[[name]] <<- obs
|
||||
# Default is to suspend when hidden
|
||||
.outputOptions[[name]][['suspendWhenHidden']] <<- TRUE
|
||||
}
|
||||
else {
|
||||
stop(paste("Unexpected", class(func), "output for", name))
|
||||
@@ -147,7 +160,8 @@ ShinyApp <- setRefClass(
|
||||
},
|
||||
.write = function(json) {
|
||||
if (getOption('shiny.trace', FALSE))
|
||||
message('SEND ', json)
|
||||
message('SEND ',
|
||||
gsub('(?m)base64,[a-zA-Z0-9+/=]+','[base64 data]',json,perl=TRUE))
|
||||
if (getOption('shiny.transcode.json', TRUE))
|
||||
json <- iconv(json, to='UTF-8')
|
||||
websocket_write(json, .websocket)
|
||||
@@ -228,7 +242,7 @@ ShinyApp <- setRefClass(
|
||||
return(httpResponse(404, 'text/html', '<h1>Not Found</h1>'))
|
||||
|
||||
filename <- ifelse(is.function(download$filename),
|
||||
Context$new()$run(download$filename),
|
||||
Context$new('[download]')$run(download$filename),
|
||||
download$filename)
|
||||
|
||||
# If the URL does not contain the filename, and the desired filename
|
||||
@@ -245,13 +259,11 @@ ShinyApp <- setRefClass(
|
||||
|
||||
tmpdata <- tempfile()
|
||||
on.exit(unlink(tmpdata))
|
||||
conn <- file(tmpdata, open = 'wb')
|
||||
result <- try(Context$new()$run(function() { download$func(conn) }))
|
||||
result <- try(Context$new('[download]')$run(function() { download$func(tmpdata) }))
|
||||
if (is(result, 'try-error')) {
|
||||
return(httpResponse(500, 'text/plain',
|
||||
attr(result, 'condition')$message))
|
||||
}
|
||||
close(conn)
|
||||
return(httpResponse(
|
||||
200,
|
||||
download$contentType %OR% getContentType(tools::file_ext(filename)),
|
||||
@@ -284,22 +296,126 @@ ShinyApp <- setRefClass(
|
||||
return(sprintf('session/%s/download/%s',
|
||||
URLencode(token, TRUE),
|
||||
URLencode(name, TRUE)))
|
||||
},
|
||||
# This function suspends observers for hidden outputs and resumes observers
|
||||
# for un-hidden outputs.
|
||||
manageHiddenOutputs = function() {
|
||||
# Find hidden state for each output, and suspend/resume accordingly
|
||||
for (outputName in names(.outputs)) {
|
||||
# Find corresponding hidden state input variable, with the format
|
||||
# ".shinyout_foo_hidden".
|
||||
# Some tricky stuff: instead of accessing names using session$names(),
|
||||
# get the names directly via session$.values, to avoid triggering reactivity.
|
||||
# Need to handle cases where the output object isn't actually used
|
||||
# in the web page; in these cases, there's no .shinyout_foo_hidden flag,
|
||||
# and hidden should be TRUE. In other words, NULL and TRUE should map
|
||||
# to TRUE, FALSE should map to FALSE.
|
||||
hidden <- session$.values[[paste(".shinyout_", outputName, "_hidden", sep="")]]
|
||||
if (is.null(hidden)) hidden <- TRUE
|
||||
|
||||
if (hidden && .outputOptions[[outputName]][['suspendWhenHidden']]) {
|
||||
.outputs[[outputName]]$suspend()
|
||||
} else {
|
||||
.outputs[[outputName]]$resume()
|
||||
}
|
||||
}
|
||||
},
|
||||
outputOptions = function(name, ...) {
|
||||
# If no name supplied, return the list of options for all outputs
|
||||
if (is.null(name))
|
||||
return(.outputOptions)
|
||||
if (! name %in% names(.outputs))
|
||||
stop(name, " is not in list of output objects")
|
||||
|
||||
opts <- list(...)
|
||||
# If no options are set, return the options for the specified output
|
||||
if (length(opts) == 0)
|
||||
return(.outputOptions[[name]])
|
||||
|
||||
# Set the appropriate option
|
||||
validOpts <- "suspendWhenHidden"
|
||||
for (optname in names(opts)) {
|
||||
if (! optname %in% validOpts)
|
||||
stop(optname, " is not a valid option")
|
||||
|
||||
.outputOptions[[name]][[optname]] <<- opts[[optname]]
|
||||
}
|
||||
|
||||
# If any changes to suspendWhenHidden, need to re-run manageHiddenOutputs
|
||||
if ("suspendWhenHidden" %in% names(opts)) {
|
||||
manageHiddenOutputs()
|
||||
}
|
||||
|
||||
invisible()
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
.createOutputWriter <- function(shinyapp) {
|
||||
ow <- list(impl=shinyapp)
|
||||
class(ow) <- 'shinyoutput'
|
||||
return(ow)
|
||||
structure(list(impl=shinyapp), class='shinyoutput')
|
||||
}
|
||||
|
||||
#' @S3method $<- shinyoutput
|
||||
`$<-.shinyoutput` <- function(x, name, value) {
|
||||
x[['impl']]$defineOutput(name, value)
|
||||
.subset2(x, 'impl')$defineOutput(name, value, deparse(substitute(value)))
|
||||
return(invisible(x))
|
||||
}
|
||||
|
||||
#' @S3method [[<- shinyoutput
|
||||
`[[<-.shinyoutput` <- `$<-.shinyoutput`
|
||||
|
||||
#' @S3method $ shinyoutput
|
||||
`$.shinyoutput` <- function(x, name) {
|
||||
stop("Reading objects from shinyoutput object not allowed.")
|
||||
}
|
||||
|
||||
#' @S3method [[ shinyoutput
|
||||
`[[.shinyoutput` <- `$.shinyoutput`
|
||||
|
||||
#' @S3method [ shinyoutput
|
||||
`[.shinyoutput` <- function(values, name) {
|
||||
stop("Single-bracket indexing of shinyoutput object is not allowed.")
|
||||
}
|
||||
|
||||
#' @S3method [<- shinyoutput
|
||||
`[<-.shinyoutput` <- function(values, name, value) {
|
||||
stop("Single-bracket indexing of shinyoutput object is not allowed.")
|
||||
}
|
||||
|
||||
#' Set options for an output object.
|
||||
#'
|
||||
#' These are the available options for an output object:
|
||||
#' \itemize{
|
||||
#' \item suspendWhenHidden. When \code{TRUE} (the default), the output object
|
||||
#' will be suspended (not execute) when it is hidden on the web page. When
|
||||
#' \code{FALSE}, the output object will not suspend when hidden, and if it
|
||||
#' was already hidden and suspended, then it will resume immediately.
|
||||
#' }
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Get the list of options for all observers within output
|
||||
#' outputOptions(output)
|
||||
#'
|
||||
#' # Disable suspend for output$myplot
|
||||
#' outputOptions(output, "myplot", suspendWhenHidden = FALSE)
|
||||
#'
|
||||
#' # Get the list of options for output$myplot
|
||||
#' outputOptions(output, "myplot")
|
||||
#' }
|
||||
#'
|
||||
#' @param x A shinyoutput object (typically \code{output}).
|
||||
#' @param name The name of an output observer in the shinyoutput object.
|
||||
#' @param ... Options to set for the output observer.
|
||||
#' @export
|
||||
outputOptions <- function(x, name, ...) {
|
||||
if (!inherits(x, "shinyoutput"))
|
||||
stop("x must be a shinyoutput object.")
|
||||
|
||||
.subset2(x, 'impl')$outputOptions(name, ...)
|
||||
}
|
||||
|
||||
|
||||
resolve <- function(dir, relpath) {
|
||||
abs.path <- file.path(dir, relpath)
|
||||
if (!file.exists(abs.path))
|
||||
@@ -318,13 +434,27 @@ resolve <- function(dir, relpath) {
|
||||
httpResponse <- function(status = 200,
|
||||
content_type = "text/html; charset=UTF-8",
|
||||
content = "",
|
||||
headers = c()) {
|
||||
headers = list()) {
|
||||
# Make sure it's a list, not a vector
|
||||
headers <- as.list(headers)
|
||||
if (is.null(headers$`X-UA-Compatible`))
|
||||
headers$`X-UA-Compatible` <- "chrome=1"
|
||||
resp <- list(status = status, content_type = content_type, content = content,
|
||||
headers = headers)
|
||||
class(resp) <- 'httpResponse'
|
||||
return(resp)
|
||||
}
|
||||
|
||||
fixupRequestPath <- function(header) {
|
||||
# Separate the path from the query
|
||||
pathEnd <- regexpr('?', header$RESOURCE, fixed=TRUE)
|
||||
if (pathEnd > 0)
|
||||
header$PATH <- substring(header$RESOURCE, 1, pathEnd - 1)
|
||||
else
|
||||
header$PATH <- header$RESOURCE
|
||||
return(header)
|
||||
}
|
||||
|
||||
httpServer <- function(handlers) {
|
||||
handler <- joinHandlers(handlers)
|
||||
|
||||
@@ -333,6 +463,8 @@ httpServer <- function(handlers) {
|
||||
filter <- function(ws, header, response) response
|
||||
|
||||
function(ws, header) {
|
||||
header <- fixupRequestPath(header)
|
||||
|
||||
response <- handler(ws, header)
|
||||
if (is.null(response))
|
||||
response <- httpResponse(404, content="<h1>Not Found</h1>")
|
||||
@@ -374,7 +506,7 @@ joinHandlers <- function(handlers) {
|
||||
}
|
||||
|
||||
sessionHandler <- function(ws, header) {
|
||||
path <- header$RESOURCE
|
||||
path <- header$PATH
|
||||
if (is.null(path))
|
||||
return(NULL)
|
||||
|
||||
@@ -412,7 +544,7 @@ dynamicHandler <- function(filePath, dependencyFiles=filePath) {
|
||||
if (file.exists(filePath)) {
|
||||
local({
|
||||
cacheContext$with(function() {
|
||||
source(filePath, local=TRUE)
|
||||
source(filePath, local=new.env(parent=.GlobalEnv))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -426,7 +558,7 @@ dynamicHandler <- function(filePath, dependencyFiles=filePath) {
|
||||
|
||||
staticHandler <- function(root) {
|
||||
return(function(ws, header) {
|
||||
path <- header$RESOURCE
|
||||
path <- header$PATH
|
||||
|
||||
if (is.null(path))
|
||||
return(httpResponse(400, content="<h1>Bad Request</h1>"))
|
||||
@@ -539,6 +671,7 @@ resourcePathHandler <- function(ws, header) {
|
||||
suffix <- substr(path, 2 + len, nchar(path))
|
||||
|
||||
header$RESOURCE <- suffix
|
||||
header <- fixupRequestPath(header)
|
||||
|
||||
return(resInfo$func(ws, header))
|
||||
}
|
||||
@@ -569,7 +702,7 @@ resourcePathHandler <- function(ws, header) {
|
||||
#' # A very simple Shiny app that takes a message from the user
|
||||
#' # and outputs an uppercase version of it.
|
||||
#' shinyServer(function(input, output) {
|
||||
#' output$uppercase <- reactiveText(function() {
|
||||
#' output$uppercase <- renderText({
|
||||
#' toupper(input$message)
|
||||
#' })
|
||||
#' })
|
||||
@@ -659,7 +792,7 @@ startApp <- function(port=8101L) {
|
||||
serverFileTimestamp <- NULL
|
||||
local({
|
||||
serverFileTimestamp <<- file.info(serverR)$mtime
|
||||
source(serverR, local=TRUE)
|
||||
source(serverR, local=new.env(parent=.GlobalEnv))
|
||||
if (is.null(.globals$server))
|
||||
stop("No server was defined in server.R")
|
||||
})
|
||||
@@ -715,11 +848,20 @@ startApp <- function(port=8101L) {
|
||||
msg$data[[ splitName[[1]] ]] <- switch(
|
||||
splitName[[2]],
|
||||
matrix = unpackMatrix(val),
|
||||
number = ifelse(is.null(val), NA, val),
|
||||
stop('Unknown type specified for ', name)
|
||||
)
|
||||
}
|
||||
else if (is.list(val) && is.null(names(val)))
|
||||
msg$data[[name]] <- unlist(val, recursive=FALSE)
|
||||
else if (is.list(val) && is.null(names(val))) {
|
||||
val_flat <- unlist(val, recursive = TRUE)
|
||||
|
||||
if (is.null(val_flat)) {
|
||||
# This is to assign NULL instead of deleting the item
|
||||
msg$data[name] <- list(NULL)
|
||||
} else {
|
||||
msg$data[[name]] <- val_flat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,7 +875,7 @@ startApp <- function(port=8101L) {
|
||||
shinyServer(NULL)
|
||||
local({
|
||||
serverFileTimestamp <<- mtime
|
||||
source(serverR, local=TRUE)
|
||||
source(serverR, local=new.env(parent=.GlobalEnv))
|
||||
if (is.null(.globals$server))
|
||||
stop("No server was defined in server.R")
|
||||
})
|
||||
@@ -743,9 +885,8 @@ startApp <- function(port=8101L) {
|
||||
shinyapp$allowDataUriScheme <- msg$data[['__allowDataUriScheme']]
|
||||
msg$data[['__allowDataUriScheme']] <- NULL
|
||||
shinyapp$session$mset(msg$data)
|
||||
flushReact()
|
||||
local({
|
||||
serverFunc(input=.createValuesReader(shinyapp$session),
|
||||
serverFunc(input=.createReactiveValues(shinyapp$session, readonly=TRUE),
|
||||
output=.createOutputWriter(shinyapp))
|
||||
})
|
||||
},
|
||||
@@ -754,8 +895,12 @@ startApp <- function(port=8101L) {
|
||||
},
|
||||
shinyapp$dispatch(msg)
|
||||
)
|
||||
shinyapp$manageHiddenOutputs()
|
||||
flushReact()
|
||||
shinyapp$flushOutput()
|
||||
lapply(apps$values(), function(shinyapp) {
|
||||
shinyapp$flushOutput()
|
||||
NULL
|
||||
})
|
||||
}, ws_env)
|
||||
|
||||
message('\n', 'Listening on port ', port)
|
||||
@@ -770,11 +915,15 @@ startApp <- function(port=8101L) {
|
||||
# @param ws_env The return value from \code{\link{startApp}}.
|
||||
serviceApp <- function(ws_env) {
|
||||
if (timerCallbacks$executeElapsed()) {
|
||||
for (shinyapp in apps$values()) {
|
||||
shinyapp$manageHiddenOutputs()
|
||||
}
|
||||
|
||||
flushReact()
|
||||
lapply(apps$values(), function(shinyapp) {
|
||||
shinyapp$flushOutput()
|
||||
NULL
|
||||
})
|
||||
|
||||
for (shinyapp in apps$values()) {
|
||||
shinyapp$flushOutput()
|
||||
}
|
||||
}
|
||||
|
||||
# If this R session is interactive, then call service() with a short timeout
|
||||
@@ -811,7 +960,9 @@ runApp <- function(appDir=getwd(),
|
||||
|
||||
orig.wd <- getwd()
|
||||
setwd(appDir)
|
||||
on.exit(setwd(orig.wd))
|
||||
on.exit(setwd(orig.wd), add = TRUE)
|
||||
|
||||
require(shiny)
|
||||
|
||||
ws_env <- startApp(port=port)
|
||||
|
||||
@@ -874,8 +1025,8 @@ runExample <- function(example=NA,
|
||||
# The only difference is that, if the protocol is https, it changes the
|
||||
# download settings, depending on platform.
|
||||
download <- function(url, ...) {
|
||||
# First, check protocol. If https, check platform:
|
||||
if (grepl('^https://', url)) {
|
||||
# First, check protocol. If http or https, check platform:
|
||||
if (grepl('^https?://', url)) {
|
||||
|
||||
# If Windows, call setInternet2, then use download.file with defaults.
|
||||
if (.Platform$OS.type == "windows") {
|
||||
@@ -918,53 +1069,3 @@ download <- function(url, ...) {
|
||||
download.file(url, ...)
|
||||
}
|
||||
}
|
||||
|
||||
#' Run a Shiny application from https://gist.github.com
|
||||
#'
|
||||
#' Download and launch a Shiny application that is hosted on GitHub as a gist.
|
||||
#'
|
||||
#' @param gist The identifier of the gist. For example, if the gist is
|
||||
#' https://gist.github.com/3239667, then \code{3239667}, \code{'3239667'}, and
|
||||
#' \code{'https://gist.github.com/3239667'} are all valid values.
|
||||
#' @param port The TCP port that the application should listen on. Defaults to
|
||||
#' port 8100.
|
||||
#' @param launch.browser If true, the system's default web browser will be
|
||||
#' launched automatically after the app is started. Defaults to true in
|
||||
#' interactive sessions only.
|
||||
#'
|
||||
#' @export
|
||||
runGist <- function(gist,
|
||||
port=8100L,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive())) {
|
||||
|
||||
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
|
||||
sprintf('https://gist.github.com/gists/%s/download', gist)
|
||||
} else if(grepl('^https://gist.github.com/([0-9a-f]+)$', gist)) {
|
||||
paste(sub('https://gist.github.com/',
|
||||
'https://gist.github.com/gists/',
|
||||
gist),
|
||||
'/download',
|
||||
sep='')
|
||||
} else {
|
||||
stop('Unrecognized gist identifier format')
|
||||
}
|
||||
filePath <- tempfile('shinygist', fileext='.tar.gz')
|
||||
if (download(gistUrl, filePath, mode = "wb", quiet = TRUE) != 0)
|
||||
stop("Failed to download URL ", gistUrl)
|
||||
on.exit(unlink(filePath))
|
||||
|
||||
# Regular untar commonly causes two problems on Windows with github tarballs:
|
||||
# 1) If RTools' tar.exe is in the path, you get cygwin path warnings which
|
||||
# throw list=TRUE off;
|
||||
# 2) If the internal untar implementation is used, it chokes on the 'g'
|
||||
# type flag that github uses (to stash their commit hash info).
|
||||
# By using our own forked/modified untar2 we sidestep both issues.
|
||||
dirname <- untar2(filePath, list=TRUE)[1]
|
||||
untar2(filePath, exdir = dirname(filePath))
|
||||
|
||||
appdir <- file.path(dirname(filePath), dirname)
|
||||
on.exit(unlink(appdir, recursive = TRUE))
|
||||
|
||||
runApp(appdir, port=port, launch.browser=launch.browser)
|
||||
}
|
||||
|
||||
13
R/shinyui.R
13
R/shinyui.R
@@ -106,7 +106,7 @@ renderPage <- function(ui, connection) {
|
||||
if (isTag(content) && identical(content$name, "head")) {
|
||||
textConn <- textConnection(NULL, "w")
|
||||
textConnWriter <- function(text) cat(text, file = textConn)
|
||||
tagWriteChildren(content, textConnWriter, 1, context)
|
||||
tagWrite(content$children, textConnWriter, 1, context)
|
||||
context$head <- append(context$head, textConnectionValue(textConn))
|
||||
close(textConn)
|
||||
return (FALSE)
|
||||
@@ -126,10 +126,10 @@ renderPage <- function(ui, connection) {
|
||||
writeLines(c('<!DOCTYPE html>',
|
||||
'<html>',
|
||||
'<head>',
|
||||
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
|
||||
' <script src="shared/jquery.js" type="text/javascript"></script>',
|
||||
' <script src="shared/shiny.js" type="text/javascript"></script>',
|
||||
' <link rel="stylesheet" type="text/css" href="shared/shiny.css"/>',
|
||||
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
|
||||
' <script src="shared/jquery.js" type="text/javascript"></script>',
|
||||
' <script src="shared/shiny.js" type="text/javascript"></script>',
|
||||
' <link rel="stylesheet" type="text/css" href="shared/shiny.css"/>',
|
||||
context$head,
|
||||
'</head>',
|
||||
'<body>',
|
||||
@@ -189,7 +189,8 @@ shinyUI <- function(ui, path='/') {
|
||||
registerClient({
|
||||
|
||||
function(ws, header) {
|
||||
if (header$RESOURCE != path)
|
||||
|
||||
if (header$PATH != path)
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
|
||||
@@ -5,13 +5,19 @@ suppressPackageStartupMessages({
|
||||
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Creates a reactive plot that is suitable for assigning to an \code{output}
|
||||
#' Renders a reactive plot that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-plot-output}.
|
||||
#'
|
||||
#' For output, it will try to use the following devices, in this order:
|
||||
#' quartz (via \code{\link[grDevices]{png}}), then \code{\link[Cairo]{CairoPNG}},
|
||||
#' and finally \code{\link[grDevices]{png}}. This is in order of quality of
|
||||
#' output. Notably, plain \code{png} output on Linux and Windows may not
|
||||
#' antialias some point shapes, resulting in poor quality output.
|
||||
#'
|
||||
#' @param func A function that generates a plot.
|
||||
#' @param expr An expression that generates a plot.
|
||||
#' @param width The width of the rendered plot, in pixels; or \code{'auto'} to
|
||||
#' use the \code{offsetWidth} of the HTML element that is bound to this plot.
|
||||
#' You can also pass in a function that returns the width in pixels or
|
||||
@@ -24,23 +30,41 @@ suppressPackageStartupMessages({
|
||||
#' values and functions.
|
||||
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that generates a plot (deprecated; use \code{expr}
|
||||
#' instead).
|
||||
#'
|
||||
#' @export
|
||||
reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
renderPlot <- function(expr, width='auto', height='auto', ...,
|
||||
env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPlot: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
func <- exprToFunction(expr, env, quoted)
|
||||
}
|
||||
|
||||
|
||||
args <- list(...)
|
||||
|
||||
if (is.function(width))
|
||||
width <- reactive(width)
|
||||
widthWrapper <- reactive({ width() })
|
||||
else
|
||||
widthWrapper <- NULL
|
||||
|
||||
if (is.function(height))
|
||||
height <- reactive(height)
|
||||
heightWrapper <- reactive({ height() })
|
||||
else
|
||||
heightWrapper <- NULL
|
||||
|
||||
return(function(shinyapp, name, ...) {
|
||||
png.file <- tempfile(fileext='.png')
|
||||
|
||||
if (is.function(width))
|
||||
width <- width()
|
||||
if (is.function(height))
|
||||
height <- height()
|
||||
if (!is.null(widthWrapper))
|
||||
width <- widthWrapper()
|
||||
if (!is.null(heightWrapper))
|
||||
height <- heightWrapper()
|
||||
|
||||
# Note that these are reactive calls. A change to the width and height
|
||||
# will inherently cause a reactive plot to redraw (unless width and
|
||||
@@ -53,8 +77,21 @@ reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
return(NULL)
|
||||
|
||||
do.call(png, c(args, filename=png.file, width=width, height=height))
|
||||
|
||||
# If quartz is available, use png() (which will default to quartz).
|
||||
# Otherwise, if the Cairo package is installed, use CairoPNG().
|
||||
# Finally, if neither quartz nor Cairo, use png().
|
||||
if (capabilities("aqua")) {
|
||||
pngfun <- png
|
||||
} else if (nchar(system.file(package = "Cairo"))) {
|
||||
require(Cairo)
|
||||
pngfun <- CairoPNG
|
||||
} else {
|
||||
pngfun <- png
|
||||
}
|
||||
|
||||
do.call(pngfun, c(args, filename=png.file, width=width, height=height))
|
||||
on.exit(unlink(png.file))
|
||||
tryCatch(
|
||||
func(),
|
||||
finally=dev.off())
|
||||
@@ -83,18 +120,29 @@ reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
#' The corresponding HTML output tag should be \code{div} and have the CSS class
|
||||
#' name \code{shiny-html-output}.
|
||||
#'
|
||||
#' @param func A function that returns an R object that can be used with
|
||||
#' @param expr An expression that returns an R object that can be used with
|
||||
#' \code{\link[xtable]{xtable}}.
|
||||
#' @param ... Arguments to be passed through to \code{\link[xtable]{xtable}} and
|
||||
#' \code{\link[xtable]{print.xtable}}.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns an R object that can be used with
|
||||
#' \code{\link[xtable]{xtable}} (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @export
|
||||
reactiveTable <- function(func, ...) {
|
||||
reactive(function() {
|
||||
renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderTable: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
func <- exprToFunction(expr, env, quoted)
|
||||
}
|
||||
|
||||
function() {
|
||||
classNames <- getOption('shiny.table.class', 'data table table-bordered table-condensed')
|
||||
data <- func()
|
||||
|
||||
if (is.null(data) || is.na(data))
|
||||
if (is.null(data))
|
||||
return("")
|
||||
|
||||
return(paste(
|
||||
@@ -106,14 +154,15 @@ reactiveTable <- function(func, ...) {
|
||||
'"',
|
||||
sep=''), ...)),
|
||||
collapse="\n"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#' Printable Output
|
||||
#'
|
||||
#' Makes a reactive version of the given function that also turns its printable
|
||||
#' result into a string. The reactive function is suitable for assigning to an
|
||||
#' \code{output} slot.
|
||||
#' Makes a reactive version of the given function that captures any printed
|
||||
#' output, and also captures its printable result (unless
|
||||
#' \code{\link{invisible}}), into a string. The resulting function is suitable
|
||||
#' for assigning to an \code{output} slot.
|
||||
#'
|
||||
#' The corresponding HTML output tag can be anything (though \code{pre} is
|
||||
#' recommended if you need a monospace font and whitespace preserved) and should
|
||||
@@ -122,13 +171,37 @@ reactiveTable <- function(func, ...) {
|
||||
#' The result of executing \code{func} will be printed inside a
|
||||
#' \code{\link[utils]{capture.output}} call.
|
||||
#'
|
||||
#' @param func A function that returns a printable R object.
|
||||
#' Note that unlike most other Shiny output functions, if the given function
|
||||
#' returns \code{NULL} then \code{NULL} will actually be visible in the output.
|
||||
#' To display nothing, make your function return \code{\link{invisible}()}.
|
||||
#'
|
||||
#' @param expr An expression that may print output and/or return a printable R
|
||||
#' object.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' @param func A function that may print output and/or return a printable R
|
||||
#' object (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @seealso \code{\link{renderText}} for displaying the value returned from a
|
||||
#' function, instead of the printed output.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#' @export
|
||||
reactivePrint <- function(func) {
|
||||
reactive(function() {
|
||||
return(paste(capture.output(print(func())), collapse="\n"))
|
||||
})
|
||||
renderPrint <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPrint: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
func <- exprToFunction(expr, env, quoted)
|
||||
}
|
||||
|
||||
function() {
|
||||
return(paste(capture.output({
|
||||
result <- withVisible(func())
|
||||
if (result$visible)
|
||||
print(result$value)
|
||||
}), collapse="\n"))
|
||||
}
|
||||
}
|
||||
|
||||
#' Text Output
|
||||
@@ -144,14 +217,31 @@ reactivePrint <- function(func) {
|
||||
#' The result of executing \code{func} will passed to \code{cat}, inside a
|
||||
#' \code{\link[utils]{capture.output}} call.
|
||||
#'
|
||||
#' @param func A function that returns an R object that can be used as an
|
||||
#' @param expr An expression that returns an R object that can be used as an
|
||||
#' argument to \code{cat}.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns an R object that can be used as an
|
||||
#' argument to \code{cat}.(deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @seealso \code{\link{renderPrint}} for capturing the print output of a
|
||||
#' function, rather than the returned text value.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#' @export
|
||||
reactiveText <- function(func) {
|
||||
reactive(function() {
|
||||
return(paste(capture.output(cat(func())), collapse="\n"))
|
||||
})
|
||||
renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderText: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
func <- exprToFunction(expr, env, quoted)
|
||||
}
|
||||
|
||||
function() {
|
||||
value <- func()
|
||||
return(paste(capture.output(cat(value)), collapse="\n"))
|
||||
}
|
||||
}
|
||||
|
||||
#' UI Output
|
||||
@@ -162,27 +252,39 @@ reactiveText <- function(func) {
|
||||
#' The corresponding HTML output tag should be \code{div} and have the CSS class
|
||||
#' name \code{shiny-html-output} (or use \code{\link{uiOutput}}).
|
||||
#'
|
||||
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' @param expr An expression that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' or a list of such objects.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' or a list of such objects (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @seealso conditionalPanel
|
||||
#'
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' output$moreControls <- reactiveUI(function() {
|
||||
#' output$moreControls <- renderUI({
|
||||
#' list(
|
||||
#'
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
reactiveUI <- function(func) {
|
||||
reactive(function() {
|
||||
renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderUI: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
func <- exprToFunction(expr, env, quoted)
|
||||
}
|
||||
|
||||
function() {
|
||||
result <- func()
|
||||
if (is.null(result) || length(result) == 0)
|
||||
return(NULL)
|
||||
return(as.character(result))
|
||||
})
|
||||
# Wrap result in tagList in case it is an ordinary list
|
||||
return(as.character(tagList(result)))
|
||||
}
|
||||
}
|
||||
|
||||
#' File Downloads
|
||||
@@ -199,10 +301,10 @@ reactiveUI <- function(func) {
|
||||
#' user's web browser should default to when downloading the file; or a
|
||||
#' function that returns such a string. (Reactive values and functions may be
|
||||
#' used from this function.)
|
||||
#' @param content A function that takes a single argument \code{con} that is a
|
||||
#' file connection opened in mode \code{wb}, and writes the content of the
|
||||
#' download into the connection. (Reactive values and functions may be used
|
||||
#' from this function.)
|
||||
#' @param content A function that takes a single argument \code{file} that is a
|
||||
#' file path (string) of a nonexistent temp file, and writes the content to
|
||||
#' that file path. (Reactive values and functions may be used from this
|
||||
#' function.)
|
||||
#' @param contentType A string of the download's
|
||||
#' \href{http://en.wikipedia.org/wiki/Internet_media_type}{content type}, for
|
||||
#' example \code{"text/csv"} or \code{"image/png"}. If \code{NULL} or
|
||||
@@ -216,8 +318,8 @@ reactiveUI <- function(func) {
|
||||
#' filename = function() {
|
||||
#' paste('data-', Sys.Date(), '.csv', sep='')
|
||||
#' },
|
||||
#' content = function(con) {
|
||||
#' write.csv(data, con)
|
||||
#' content = function(file) {
|
||||
#' write.csv(data, file)
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
@@ -230,4 +332,61 @@ downloadHandler <- function(filename, content, contentType=NA) {
|
||||
return(function(shinyapp, name, ...) {
|
||||
shinyapp$registerDownload(name, filename, contentType, content)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Deprecated functions ------------------------------------------------------
|
||||
|
||||
#' Plot output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderPlot}}.
|
||||
#' @param func A function.
|
||||
#' @param width Width.
|
||||
#' @param height Height.
|
||||
#' @param ... Other arguments to pass on.
|
||||
#' @export
|
||||
reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
shinyDeprecated(new="renderPlot")
|
||||
renderPlot({ func() }, width=width, height=height, ...)
|
||||
}
|
||||
|
||||
#' Table output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderTable}}.
|
||||
#' @param func A function.
|
||||
#' @param ... Other arguments to pass on.
|
||||
#' @export
|
||||
reactiveTable <- function(func, ...) {
|
||||
shinyDeprecated(new="renderTable")
|
||||
renderTable({ func() })
|
||||
}
|
||||
|
||||
#' Print output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderPrint}}.
|
||||
#' @param func A function.
|
||||
#' @export
|
||||
reactivePrint <- function(func) {
|
||||
shinyDeprecated(new="renderPrint")
|
||||
renderPrint({ func() })
|
||||
}
|
||||
|
||||
#' UI output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderUI}}.
|
||||
#' @param func A function.
|
||||
#' @export
|
||||
reactiveUI <- function(func) {
|
||||
shinyDeprecated(new="renderUI")
|
||||
renderUI({ func() })
|
||||
}
|
||||
|
||||
#' Text output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderText}}.
|
||||
#' @param func A function.
|
||||
#' @export
|
||||
reactiveText <- function(func) {
|
||||
shinyDeprecated(new="renderText")
|
||||
renderText({ func() })
|
||||
}
|
||||
|
||||
@@ -101,13 +101,13 @@ slider <- function(inputId, min, max, value, step = NULL, ...,
|
||||
}
|
||||
|
||||
# build slider
|
||||
sliderFragment <- tags$input(
|
||||
sliderFragment <- list(tags$input(
|
||||
id=inputId, type="slider",
|
||||
name=inputId, value=paste(value, collapse=';'), class="jslider",
|
||||
'data-from'=min, 'data-to'=max, 'data-step'=step,
|
||||
'data-skin'='plastic', 'data-round'=round, 'data-locale'=locale,
|
||||
'data-format'=format, 'data-scale'=ticks,
|
||||
'data-smooth'=FALSE)
|
||||
'data-smooth'=FALSE))
|
||||
|
||||
if (identical(animate, TRUE))
|
||||
animate <- animationOptions()
|
||||
|
||||
48
R/tags.R
48
R/tags.R
@@ -61,7 +61,7 @@ as.character.shiny.tag <- function(x, ...) {
|
||||
cat(text, file=f)
|
||||
}
|
||||
tagWrite(x, textWriter)
|
||||
return(HTML(paste(readLines(f), collapse='\n')))
|
||||
return(HTML(paste(readLines(f, warn=FALSE), collapse='\n')))
|
||||
}
|
||||
|
||||
#' @S3method print shiny.tag.list
|
||||
@@ -160,23 +160,7 @@ tag <- function(`_tag_name`, varArgs) {
|
||||
return (tag)
|
||||
}
|
||||
|
||||
tagWriteChildren <- function(tag, textWriter, indent, context) {
|
||||
for (child in tag$children) {
|
||||
if (isTag(child)) {
|
||||
tagWrite(child, textWriter, indent, context)
|
||||
}
|
||||
else {
|
||||
# first call optional filter -- exit function if it returns false
|
||||
if (is.null(context) || is.null(context$filter) || context$filter(child)) {
|
||||
child <- normalizeText(child)
|
||||
indentText <- paste(rep(" ", indent*3), collapse="")
|
||||
textWriter(paste(indentText, child, "\n", sep=""))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tagWrite <- function(tag, textWriter, indent=0, context = NULL) {
|
||||
tagWrite <- function(tag, textWriter, indent=0, context = NULL, eol = "\n") {
|
||||
|
||||
# optionally process a list of tags
|
||||
if (!isTag(tag) && is.list(tag)) {
|
||||
@@ -189,7 +173,13 @@ tagWrite <- function(tag, textWriter, indent=0, context = NULL) {
|
||||
return (NULL)
|
||||
|
||||
# compute indent text
|
||||
indentText <- paste(rep(" ", indent*3), collapse="")
|
||||
indentText <- paste(rep(" ", indent*2), collapse="")
|
||||
|
||||
# Check if it's just text (may either be plain-text or HTML)
|
||||
if (is.character(tag)) {
|
||||
textWriter(paste(indentText, normalizeText(tag), eol, sep=""))
|
||||
return (NULL)
|
||||
}
|
||||
|
||||
# write tag name
|
||||
textWriter(paste(indentText, "<", tag$name, sep=""))
|
||||
@@ -210,19 +200,18 @@ tagWrite <- function(tag, textWriter, indent=0, context = NULL) {
|
||||
|
||||
# write any children
|
||||
if (length(tag$children) > 0) {
|
||||
textWriter(">")
|
||||
|
||||
# special case for a single child text node (skip newlines and indentation)
|
||||
if ((length(tag$children) == 1) && is.character(tag$children[[1]]) ) {
|
||||
if (is.null(context) || is.null(context$filter)
|
||||
|| context$filter(tag$children[[1]])) {
|
||||
text <- normalizeText(tag$children[[1]])
|
||||
textWriter(paste(">", text, "</", tag$name, ">\n", sep=""))
|
||||
}
|
||||
tagWrite(tag$children[[1]], textWriter, 0, context, "")
|
||||
textWriter(paste("</", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(">\n")
|
||||
tagWriteChildren(tag, textWriter, indent+1, context)
|
||||
textWriter(paste(indentText, "</", tag$name, ">\n", sep=""))
|
||||
textWriter("\n")
|
||||
for (child in tag$children)
|
||||
tagWrite(child, textWriter, indent+1, context)
|
||||
textWriter(paste(indentText, "</", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -231,16 +220,15 @@ tagWrite <- function(tag, textWriter, indent=0, context = NULL) {
|
||||
if (tag$name %in% c("area", "base", "br", "col", "command", "embed", "hr",
|
||||
"img", "input", "keygen", "link", "meta", "param",
|
||||
"source", "track", "wbr")) {
|
||||
textWriter("/>\n")
|
||||
textWriter(paste("/>", eol, sep=""))
|
||||
}
|
||||
else {
|
||||
textWriter(paste("></", tag$name, ">\n", sep=""))
|
||||
textWriter(paste("></", tag$name, ">", eol, sep=""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# environment used to store all available tags
|
||||
#' @export
|
||||
tags <- new.env()
|
||||
|
||||
110
R/utils.R
110
R/utils.R
@@ -101,4 +101,112 @@ knownContentTypes$mset(
|
||||
|
||||
getContentType <- function(ext, defaultType='application/octet-stream') {
|
||||
knownContentTypes$get(tolower(ext)) %OR% defaultType
|
||||
}
|
||||
}
|
||||
|
||||
# Create a zero-arg function from a quoted expression and environment
|
||||
# @examples
|
||||
# makeFunction(body=quote(print(3)))
|
||||
makeFunction <- function(args = pairlist(), body, env = parent.frame()) {
|
||||
eval(call("function", args, body), env)
|
||||
}
|
||||
|
||||
#' Convert an expression or quoted expression to a function
|
||||
#'
|
||||
#' This is to be called from another function, because it will attempt to get
|
||||
#' an unquoted expression from two calls back.
|
||||
#'
|
||||
#' If expr is a quoted expression, then this just converts it to a function.
|
||||
#' If expr is a function, then this simply returns expr (and prints a
|
||||
#' deprecation message.
|
||||
#' If expr was a non-quoted expression from two calls back, then this will
|
||||
#' quote the original expression and convert it to a function.
|
||||
#
|
||||
#' @param expr A quoted or unquoted expression, or a function.
|
||||
#' @param env The desired environment for the function. Defaults to the
|
||||
#' calling environment two steps back.
|
||||
#' @param quoted Is the expression quoted?
|
||||
#'
|
||||
#' @examples
|
||||
#' # Example of a new renderer, similar to renderText
|
||||
#' # This is something that toolkit authors will do
|
||||
#' renderTriple <- function(expr, env=parent.frame(), quoted=FALSE) {
|
||||
#' # Convert expr to a function
|
||||
#' func <- shiny::exprToFunction(expr, env, quoted)
|
||||
#'
|
||||
#' function() {
|
||||
#' value <- func()
|
||||
#' paste(rep(value, 3), collapse=", ")
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
#' # Example of using the renderer.
|
||||
#' # This is something that app authors will do.
|
||||
#' values <- reactiveValues(A="text")
|
||||
#'
|
||||
#' \dontrun{
|
||||
#' # Create an output object
|
||||
#' output$tripleA <- renderTriple({
|
||||
#' values$A
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' # At the R console, you can experiment with the renderer using isolate()
|
||||
#' tripleA <- renderTriple({
|
||||
#' values$A
|
||||
#' })
|
||||
#'
|
||||
#' isolate(tripleA())
|
||||
#' # "text, text, text"
|
||||
#'
|
||||
#' @export
|
||||
exprToFunction <- function(expr, env=parent.frame(2), quoted=FALSE) {
|
||||
# Get the quoted expr from two calls back
|
||||
expr_sub <- eval(substitute(substitute(expr)), parent.frame())
|
||||
|
||||
# Check if expr is a function, making sure not to evaluate expr, in case it
|
||||
# is actually an unquoted expression.
|
||||
# If expr is a single token, then indexing with [[ will error; if it has multiple
|
||||
# tokens, then [[ works. In the former case it will be a name object; in the
|
||||
# latter, it will be a language object.
|
||||
if (!is.name(expr_sub) && expr_sub[[1]] == as.name('function')) {
|
||||
# Get name of function that called this function
|
||||
called_fun <- sys.call(-1)[[1]]
|
||||
|
||||
shinyDeprecated(msg = paste("Passing functions to '", called_fun,
|
||||
"' is deprecated. Please use expressions instead. See ?", called_fun,
|
||||
" for more information.", sep=""))
|
||||
return(expr)
|
||||
}
|
||||
|
||||
if (quoted) {
|
||||
# expr is a quoted expression
|
||||
makeFunction(body=expr, env=env)
|
||||
} else {
|
||||
# expr is an unquoted expression
|
||||
makeFunction(body=expr_sub, env=env)
|
||||
}
|
||||
}
|
||||
|
||||
#' Print message for deprecated functions in Shiny
|
||||
#'
|
||||
#' To disable these messages, use \code{options(shiny.deprecation.messages=FALSE)}.
|
||||
#'
|
||||
#' @param new Name of replacement function.
|
||||
#' @param msg Message to print. If used, this will override the default message.
|
||||
#' @param old Name of deprecated function.
|
||||
shinyDeprecated <- function(new=NULL, msg=NULL,
|
||||
old=as.character(sys.call(sys.parent()))[1L]) {
|
||||
|
||||
if (getOption("shiny.deprecation.messages", default=TRUE) == FALSE)
|
||||
return(invisible())
|
||||
|
||||
if (is.null(msg)) {
|
||||
msg <- paste(old, "is deprecated.")
|
||||
if (!is.null(new))
|
||||
msg <- paste(msg, "Please use", new, "instead.",
|
||||
"To disable this message, run options(shiny.deprecation.messages=FALSE)")
|
||||
}
|
||||
# Similar to .Deprecated(), but print a message instead of warning
|
||||
message(msg)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ For an introduction and examples, visit the [Shiny homepage](http://www.rstudio.
|
||||
From an R console:
|
||||
|
||||
```r
|
||||
options(repos=c(RStudio="http://rstudio.org/_packages", getOption("repos")))
|
||||
install.packages("shiny")
|
||||
```
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ library(shiny)
|
||||
# Define server logic required to generate and plot a random distribution
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Function that generates a plot of the distribution. The function
|
||||
# is wrapped in a call to reactivePlot to indicate that:
|
||||
# Expression that generates a plot of the distribution. The expression
|
||||
# is wrapped in a call to renderPlot to indicate that:
|
||||
#
|
||||
# 1) It is "reactive" and therefore should be automatically
|
||||
# re-executed when inputs change
|
||||
# 2) Its output type is a plot
|
||||
#
|
||||
output$distPlot <- reactivePlot(function() {
|
||||
output$distPlot <- renderPlot({
|
||||
|
||||
# generate an rnorm distribution and plot it
|
||||
dist <- rnorm(input$obs)
|
||||
|
||||
@@ -5,7 +5,7 @@ library(datasets)
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Return the requested dataset
|
||||
datasetInput <- reactive(function() {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
@@ -13,13 +13,13 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Generate a summary of the dataset
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations
|
||||
output$view <- reactiveTable(function() {
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,47 +4,47 @@ library(datasets)
|
||||
# Define server logic required to summarize and view the selected dataset
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# By declaring databaseInput as a reactive function we ensure that:
|
||||
# By declaring databaseInput as a reactive expression we ensure that:
|
||||
#
|
||||
# 1) It is only called when the inputs it depends on changes
|
||||
# 2) The computation and result are shared by all the callers (it
|
||||
# only executes a single time)
|
||||
# 3) When the inputs change and the function is re-executed, the
|
||||
# 3) When the inputs change and the expression is re-executed, the
|
||||
# new result is compared to the previous result; if the two are
|
||||
# identical, then the callers are not notified
|
||||
#
|
||||
datasetInput <- reactive(function() {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# The output$caption is computed based on a reactive function that
|
||||
# The output$caption is computed based on a reactive expression that
|
||||
# returns input$caption. When the user changes the "caption" field:
|
||||
#
|
||||
# 1) This function is automatically called to recompute the output
|
||||
# 2) The new caption is pushed back to the browser for re-display
|
||||
#
|
||||
# Note that because the data-oriented reactive functions below don't
|
||||
# depend on input$caption, those functions are NOT called when
|
||||
# Note that because the data-oriented reactive expressions below don't
|
||||
# depend on input$caption, those expressions are NOT called when
|
||||
# input$caption changes.
|
||||
output$caption <- reactiveText(function() {
|
||||
output$caption <- renderText({
|
||||
input$caption
|
||||
})
|
||||
|
||||
# The output$summary depends on the datasetInput reactive function,
|
||||
# The output$summary depends on the datasetInput reactive expression,
|
||||
# so will be re-executed whenever datasetInput is re-executed
|
||||
# (i.e. whenever the input$dataset changes)
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# The output$view depends on both the databaseInput reactive function
|
||||
# The output$view depends on both the databaseInput reactive expression
|
||||
# and input$obs, so will be re-executed whenever input$dataset or
|
||||
# input$obs is changed.
|
||||
output$view <- reactiveTable(function() {
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,20 +11,20 @@ mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
|
||||
# Define server logic required to plot various variables against mpg
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Compute the forumla text in a reactive function since it is
|
||||
# Compute the forumla text in a reactive expression since it is
|
||||
# shared by the output$caption and output$mpgPlot functions
|
||||
formulaText <- reactive(function() {
|
||||
formulaText <- reactive({
|
||||
paste("mpg ~", input$variable)
|
||||
})
|
||||
|
||||
# Return the formula text for printing as a caption
|
||||
output$caption <- reactiveText(function() {
|
||||
output$caption <- renderText({
|
||||
formulaText()
|
||||
})
|
||||
|
||||
# Generate a plot of the requested variable against mpg and only
|
||||
# include outliers if requested
|
||||
output$mpgPlot <- reactivePlot(function() {
|
||||
output$mpgPlot <- renderPlot({
|
||||
boxplot(as.formula(formulaText()),
|
||||
data = mpgData,
|
||||
outline = input$outliers)
|
||||
|
||||
@@ -3,8 +3,8 @@ library(shiny)
|
||||
# Define server logic for slider examples
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Reactive function to compose a data frame containing all of the values
|
||||
sliderValues <- reactive(function() {
|
||||
# Reactive expression to compose a data frame containing all of the values
|
||||
sliderValues <- reactive({
|
||||
|
||||
# Compose data frame
|
||||
data.frame(
|
||||
@@ -22,7 +22,7 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Show the values using an HTML table
|
||||
output$values <- reactiveTable(function() {
|
||||
output$values <- renderTable({
|
||||
sliderValues()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,10 +3,10 @@ library(shiny)
|
||||
# Define server logic for random distribution application
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Reactive function to generate the requested distribution. This is
|
||||
# Reactive expression to generate the requested distribution. This is
|
||||
# called whenever the inputs change. The output functions defined
|
||||
# below then all use the value computed from this function
|
||||
data <- reactive(function() {
|
||||
# below then all use the value computed from this expression
|
||||
data <- reactive({
|
||||
dist <- switch(input$dist,
|
||||
norm = rnorm,
|
||||
unif = runif,
|
||||
@@ -19,9 +19,9 @@ shinyServer(function(input, output) {
|
||||
|
||||
# Generate a plot of the data. Also uses the inputs to build the
|
||||
# plot label. Note that the dependencies on both the inputs and
|
||||
# the data reactive function are both tracked, and all functions
|
||||
# the data reactive expression are both tracked, and all expressions
|
||||
# are called in the sequence implied by the dependency graph
|
||||
output$plot <- reactivePlot(function() {
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
@@ -30,12 +30,12 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Generate a summary of the data
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
summary(data())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the data
|
||||
output$table <- reactiveTable(function() {
|
||||
output$table <- renderTable({
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ library(datasets)
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Return the requested dataset
|
||||
datasetInput <- reactive(function() {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
@@ -13,13 +13,13 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Generate a summary of the dataset
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations
|
||||
output$view <- reactiveTable(function() {
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,10 +3,10 @@ library(shiny)
|
||||
# Define server logic for random distribution application
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Reactive function to generate the requested distribution. This is
|
||||
# called whenever the inputs change. The output functions defined
|
||||
# below then all used the value computed from this function
|
||||
data <- reactive(function() {
|
||||
# Reactive expression to generate the requested distribution. This is
|
||||
# called whenever the inputs change. The output expressions defined
|
||||
# below then all used the value computed from this expression
|
||||
data <- reactive({
|
||||
dist <- switch(input$dist,
|
||||
norm = rnorm,
|
||||
unif = runif,
|
||||
@@ -19,9 +19,9 @@ shinyServer(function(input, output) {
|
||||
|
||||
# Generate a plot of the data. Also uses the inputs to build the
|
||||
# plot label. Note that the dependencies on both the inputs and
|
||||
# the data reactive function are both tracked, and all functions
|
||||
# the data reactive expression are both tracked, and all expressions
|
||||
# are called in the sequence implied by the dependency graph
|
||||
output$plot <- reactivePlot(function() {
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
@@ -30,12 +30,12 @@ shinyServer(function(input, output) {
|
||||
})
|
||||
|
||||
# Generate a summary of the data
|
||||
output$summary <- reactivePrint(function() {
|
||||
output$summary <- renderPrint({
|
||||
summary(data())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the data
|
||||
output$table <- reactiveTable(function() {
|
||||
output$table <- renderTable({
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
library(shiny)
|
||||
|
||||
shinyServer(function(input, output) {
|
||||
output$contents <- reactiveTable(function() {
|
||||
output$contents <- renderTable({
|
||||
|
||||
# input$file1 will be NULL initially. After the user selects and uploads a
|
||||
# file, it will be a data frame with 'name', 'size', 'type', and 'data'
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
shinyServer(function(input, output) {
|
||||
datasetInput <- reactive(function() {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
output$table <- reactiveTable(function() {
|
||||
output$table <- renderTable({
|
||||
datasetInput()
|
||||
})
|
||||
|
||||
output$downloadData <- downloadHandler(
|
||||
filename = function() { paste(input$dataset, '.csv', sep='') },
|
||||
content = function(conn) {
|
||||
write.csv(datasetInput(), conn)
|
||||
})
|
||||
content = function(file) {
|
||||
write.csv(datasetInput(), file)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
shinyUI(pageWithSidebar(
|
||||
headerPanel('Download Example'),
|
||||
headerPanel('Downloading Data'),
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
17
inst/tests/test-bootstrap.r
Normal file
17
inst/tests/test-bootstrap.r
Normal file
@@ -0,0 +1,17 @@
|
||||
context("bootstrap")
|
||||
|
||||
test_that("CSS unit validation", {
|
||||
# On error, return NA; on success, return result
|
||||
validateCssUnit_wrap <- function(x) {
|
||||
tryCatch(validateCssUnit(x), error = function(e) { NA_character_ })
|
||||
}
|
||||
|
||||
# Test strings and expected results
|
||||
strings <- c("100x", "10px", "10.4px", ".4px", "1px0", "px", "5", "%", "5%", "auto", "1auto", "")
|
||||
expected <- c(NA, "10px", "10.4px", ".4px", NA, NA, NA, NA, "5%", "auto", NA, NA)
|
||||
results <- vapply(strings, validateCssUnit_wrap, character(1), USE.NAMES = FALSE)
|
||||
expect_equal(results, expected)
|
||||
|
||||
# Numbers should return string with "px"
|
||||
expect_equal(validateCssUnit(100), "100px")
|
||||
})
|
||||
73
inst/tests/test-gc.r
Normal file
73
inst/tests/test-gc.r
Normal file
@@ -0,0 +1,73 @@
|
||||
context("garbage collection")
|
||||
|
||||
test_that("unreferenced observers are garbage collected", {
|
||||
vals_removed <- FALSE
|
||||
obs_removed <- FALSE
|
||||
vals <- reactiveValues(A=1)
|
||||
obs <- observe({ vals$A })
|
||||
|
||||
# These are called when the objects are garbage-collected
|
||||
reg.finalizer(attr(.subset2(vals,'impl'), ".xData"),
|
||||
function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(attr(obs, ".xData"),
|
||||
function(e) obs_removed <<- TRUE)
|
||||
|
||||
flushReact()
|
||||
|
||||
# Removing this reference to obs doesn't delete it because vals still has a
|
||||
# reference to it
|
||||
rm(obs)
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
|
||||
|
||||
# Updating vals$A and flushing won't make obs go away because it creates a new
|
||||
# context, and vals$A's context tracks obs's context as a dependent
|
||||
vals$A <- 2
|
||||
flushReact()
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
|
||||
|
||||
# Removing vals will result in vals and obs being garbage collected since
|
||||
# there are no other references to them
|
||||
rm(vals)
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(TRUE, TRUE))
|
||||
})
|
||||
|
||||
|
||||
test_that("suspended observers are garbage collected", {
|
||||
vals_removed <- FALSE
|
||||
obs_removed <- FALSE
|
||||
vals <- reactiveValues(A=1)
|
||||
obs <- observe({ vals$A })
|
||||
|
||||
# These are called when the objects are garbage-collected
|
||||
reg.finalizer(attr(.subset2(vals,'impl'), ".xData"),
|
||||
function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(attr(obs, ".xData"),
|
||||
function(e) obs_removed <<- TRUE)
|
||||
|
||||
flushReact()
|
||||
|
||||
vals$A <- 2
|
||||
flushReact()
|
||||
invisible(gc())
|
||||
|
||||
# Simply suspending and removing our reference to obs doesn't result in GC,
|
||||
# because vals's context still has a reference to obs's context, as a dependent
|
||||
obs$suspend()
|
||||
rm(obs)
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
|
||||
|
||||
# Next time we update vals$A and flush, there's no more reference to obs
|
||||
vals$A <- 3
|
||||
flushReact()
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, TRUE))
|
||||
|
||||
# Deleting vals should work immediately now
|
||||
rm(vals)
|
||||
invisible(gc()) # Removes vals object
|
||||
expect_equal(c(vals_removed, obs_removed), c(TRUE, TRUE))
|
||||
})
|
||||
652
inst/tests/test-reactivity.r
Normal file
652
inst/tests/test-reactivity.r
Normal file
@@ -0,0 +1,652 @@
|
||||
context("reactivity")
|
||||
|
||||
|
||||
# Test for correct behavior of ReactiveValues
|
||||
test_that("ReactiveValues", {
|
||||
# Creation and indexing into ReactiveValues -------------------------------
|
||||
values <- reactiveValues()
|
||||
|
||||
# $ indexing
|
||||
values$a <- 3
|
||||
expect_equal(isolate(values$a), 3)
|
||||
|
||||
# [[ indexing
|
||||
values[['a']] <- 4
|
||||
expect_equal(isolate(values[['a']]), 4)
|
||||
|
||||
# Create with initialized values
|
||||
values <- reactiveValues(a=1, b=2)
|
||||
expect_equal(isolate(values$a), 1)
|
||||
expect_equal(isolate(values[['b']]), 2)
|
||||
|
||||
# NULL values -------------------------------------------------------------
|
||||
# Initializing with NULL value
|
||||
values <- reactiveValues(a=NULL, b=2)
|
||||
# a should exist and be NULL
|
||||
expect_equal(isolate(names(values)), c("a", "b"))
|
||||
expect_true(is.null(isolate(values$a)))
|
||||
|
||||
# Assigning NULL should keep object (not delete it), and set value to NULL
|
||||
values$b <- NULL
|
||||
expect_equal(isolate(names(values)), c("a", "b"))
|
||||
expect_true(is.null(isolate(values$b)))
|
||||
|
||||
|
||||
# Errors -----------------------------------------------------------------
|
||||
# Error: indexing with non-string
|
||||
expect_error(isolate(values[[1]]))
|
||||
expect_error(isolate(values[[NULL]]))
|
||||
expect_error(isolate(values[[list('a')]]))
|
||||
|
||||
# Error: [ indexing shouldn't work
|
||||
expect_error(isolate(values['a']))
|
||||
expect_error(isolate(values['a'] <- 1))
|
||||
|
||||
# Error: unnamed arguments
|
||||
expect_error(reactiveValues(1))
|
||||
expect_error(reactiveValues(1, b=2))
|
||||
|
||||
# Error: assignment to readonly values
|
||||
values <- .createReactiveValues(ReactiveValues$new(), readonly = TRUE)
|
||||
expect_error(values$a <- 1)
|
||||
})
|
||||
|
||||
|
||||
# Test for overreactivity. funcB has an indirect dependency on valueA (via
|
||||
# funcA) and also a direct dependency on valueA. When valueA changes, funcB
|
||||
# should only execute once.
|
||||
test_that("Functions are not over-reactive", {
|
||||
|
||||
values <- reactiveValues(A=10)
|
||||
|
||||
funcA <- reactive({
|
||||
values$A
|
||||
})
|
||||
|
||||
funcB <- reactive({
|
||||
funcA()
|
||||
values$A
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 11
|
||||
flushReact()
|
||||
expect_equal(execCount(funcB), 2)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
})
|
||||
|
||||
## "foo => bar" is defined as "foo is a dependency of bar"
|
||||
##
|
||||
## vA => fB
|
||||
## (fB, vA) => obsE
|
||||
## (fB, vA) => obsF
|
||||
##
|
||||
## obsE and obsF should each execute once when vA changes.
|
||||
test_that("overreactivity2", {
|
||||
# ----------------------------------------------
|
||||
# Test 1
|
||||
# B depends on A, and observer depends on A and B. The observer uses A and
|
||||
# B, in that order.
|
||||
|
||||
# This is to store the value from observe()
|
||||
observed_value1 <- NA
|
||||
observed_value2 <- NA
|
||||
|
||||
values <- reactiveValues(A=1)
|
||||
funcB <- reactive({
|
||||
values$A + 5
|
||||
})
|
||||
obsC <- observe({
|
||||
observed_value1 <<- funcB() * values$A
|
||||
})
|
||||
obsD <- observe({
|
||||
observed_value2 <<- funcB() * values$A
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(observed_value1, 6) # Should be 1 * (1 + 5) = 6
|
||||
expect_equal(observed_value2, 6) # Should be 1 * (1 + 5) = 6
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
expect_equal(execCount(obsD), 1)
|
||||
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
expect_equal(observed_value1, 14) # Should be 2 * (2 + 5) = 14
|
||||
expect_equal(observed_value2, 14) # Should be 2 * (2 + 5) = 14
|
||||
expect_equal(execCount(funcB), 2)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
expect_equal(execCount(obsD), 2)
|
||||
})
|
||||
|
||||
## Test for isolation. funcB depends on funcA depends on valueA. When funcA
|
||||
## is invalidated, if its new result is not different than its old result,
|
||||
## then it doesn't invalidate its dependents. This is done by adding an observer
|
||||
## (valueB) between obsA and funcC.
|
||||
##
|
||||
## valueA => obsB => valueC => funcD => obsE
|
||||
test_that("isolation", {
|
||||
values <- reactiveValues(A=10, C=NULL)
|
||||
|
||||
obsB <- observe({
|
||||
values$C <- values$A > 0
|
||||
})
|
||||
|
||||
funcD <- reactive({
|
||||
values$C
|
||||
})
|
||||
|
||||
obsE <- observe({
|
||||
funcD()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
countD <- execCount(funcD)
|
||||
|
||||
values$A <- 11
|
||||
flushReact()
|
||||
expect_equal(execCount(funcD), countD)
|
||||
})
|
||||
|
||||
|
||||
## Test for laziness. With lazy evaluation, the observers should "pull" values
|
||||
## from their dependent functions. In contrast, eager evaluation would have
|
||||
## reactive values and functions "push" their changes down to their descendents.
|
||||
test_that("laziness", {
|
||||
|
||||
values <- reactiveValues(A=10)
|
||||
|
||||
funcA <- reactive({
|
||||
values$A > 0
|
||||
})
|
||||
|
||||
funcB <- reactive({
|
||||
funcA()
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
if (values$A > 10)
|
||||
return()
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 11
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
})
|
||||
|
||||
|
||||
## Suppose B depends on A and C depends on A and B. Then when A is changed,
|
||||
## the evaluation order should be A, B, C. Also, each time A is changed, B and
|
||||
## C should be run once, if we want to be maximally efficient.
|
||||
test_that("order of evaluation", {
|
||||
# ----------------------------------------------
|
||||
# Test 1
|
||||
# B depends on A, and observer depends on A and B. The observer uses A and
|
||||
# B, in that order.
|
||||
|
||||
# This is to store the value from observe()
|
||||
observed_value <- NA
|
||||
|
||||
values <- reactiveValues(A=1)
|
||||
funcB <- reactive({
|
||||
values$A + 5
|
||||
})
|
||||
obsC <- observe({
|
||||
observed_value <<- values$A * funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(observed_value, 6) # Should be 1 * (1 + 5) = 6
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
expect_equal(observed_value, 14) # Should be 2 * (2 + 5) = 14
|
||||
expect_equal(execCount(funcB), 2)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
|
||||
|
||||
# ----------------------------------------------
|
||||
# Test 2:
|
||||
# Same as Test 1, except the observer uses A and B in reversed order.
|
||||
# Resulting values should be the same.
|
||||
|
||||
observed_value <- NA
|
||||
|
||||
values <- reactiveValues(A=1)
|
||||
funcB <- reactive({
|
||||
values$A + 5
|
||||
})
|
||||
obsC <- observe({
|
||||
observed_value <<- funcB() * values$A
|
||||
})
|
||||
|
||||
flushReact()
|
||||
# Should be 1 * (1 + 5) = 6
|
||||
expect_equal(observed_value, 6)
|
||||
expect_equal(execCount(funcB), 1)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
# Should be 2 * (2 + 5) = 14
|
||||
expect_equal(observed_value, 14)
|
||||
expect_equal(execCount(funcB), 2)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
})
|
||||
|
||||
|
||||
## Expressions in isolate() should not invalidate the parent context.
|
||||
test_that("isolate() blocks invalidations from propagating", {
|
||||
|
||||
obsC_value <- NA
|
||||
obsD_value <- NA
|
||||
|
||||
values <- reactiveValues(A=1, B=10)
|
||||
funcB <- reactive({
|
||||
values$B + 100
|
||||
})
|
||||
|
||||
# References to valueB and funcB are isolated
|
||||
obsC <- observe({
|
||||
obsC_value <<-
|
||||
values$A + isolate(values$B) + isolate(funcB())
|
||||
})
|
||||
|
||||
# In contrast with obsC, this has a non-isolated reference to funcB
|
||||
obsD <- observe({
|
||||
obsD_value <<-
|
||||
values$A + isolate(values$B) + funcB()
|
||||
})
|
||||
|
||||
|
||||
flushReact()
|
||||
expect_equal(obsC_value, 121)
|
||||
expect_equal(execCount(obsC), 1)
|
||||
expect_equal(obsD_value, 121)
|
||||
expect_equal(execCount(obsD), 1)
|
||||
|
||||
# Changing A should invalidate obsC and obsD
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
expect_equal(obsC_value, 122)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
expect_equal(obsD_value, 122)
|
||||
expect_equal(execCount(obsD), 2)
|
||||
|
||||
# Changing B shouldn't invalidate obsC becuause references to B are in isolate()
|
||||
# But it should invalidate obsD.
|
||||
values$B <- 20
|
||||
flushReact()
|
||||
expect_equal(obsC_value, 122)
|
||||
expect_equal(execCount(obsC), 2)
|
||||
expect_equal(obsD_value, 142)
|
||||
expect_equal(execCount(obsD), 3)
|
||||
|
||||
# Changing A should invalidate obsC and obsD, and they should see updated
|
||||
# values for valueA, valueB, and funcB
|
||||
values$A <- 3
|
||||
flushReact()
|
||||
expect_equal(obsC_value, 143)
|
||||
expect_equal(execCount(obsC), 3)
|
||||
expect_equal(obsD_value, 143)
|
||||
expect_equal(execCount(obsD), 4)
|
||||
})
|
||||
|
||||
|
||||
test_that("isolate() evaluates expressions in calling environment", {
|
||||
outside <- 1
|
||||
inside <- 1
|
||||
loc <- 1
|
||||
|
||||
outside <- isolate(2) # Assignment outside isolate
|
||||
isolate(inside <- 2) # Assignment inside isolate
|
||||
# Should affect vars in the calling environment
|
||||
expect_equal(outside, 2)
|
||||
expect_equal(inside, 2)
|
||||
|
||||
isolate(local(loc <<- 2)) # <<- inside isolate(local)
|
||||
isolate(local(loc <- 3)) # <- inside isolate(local) - should have no effect
|
||||
expect_equal(loc, 2)
|
||||
})
|
||||
|
||||
|
||||
test_that("Circular refs/reentrancy in reactive functions work", {
|
||||
|
||||
values <- reactiveValues(A=3)
|
||||
|
||||
funcB <- reactive({
|
||||
# Each time fB executes, it reads and then writes valueA,
|
||||
# effectively invalidating itself--until valueA becomes 0.
|
||||
if (values$A == 0)
|
||||
return()
|
||||
values$A <- values$A - 1
|
||||
return(values$A)
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 4)
|
||||
|
||||
values$A <- 3
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 8)
|
||||
|
||||
})
|
||||
|
||||
test_that("Simple recursion", {
|
||||
|
||||
values <- reactiveValues(A=5)
|
||||
funcB <- reactive({
|
||||
if (values$A == 0)
|
||||
return(0)
|
||||
values$A <- values$A - 1
|
||||
funcB()
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 2)
|
||||
expect_equal(execCount(funcB), 6)
|
||||
})
|
||||
|
||||
test_that("Non-reactive recursion", {
|
||||
nonreactiveA <- 3
|
||||
outputD <- NULL
|
||||
|
||||
funcB <- reactive({
|
||||
if (nonreactiveA == 0)
|
||||
return(0)
|
||||
nonreactiveA <<- nonreactiveA - 1
|
||||
return(funcB())
|
||||
})
|
||||
obsC <- observe({
|
||||
outputD <<- funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(funcB), 4)
|
||||
expect_equal(outputD, 0)
|
||||
})
|
||||
|
||||
test_that("Circular dep with observer only", {
|
||||
|
||||
values <- reactiveValues(A=3)
|
||||
obsB <- observe({
|
||||
if (values$A == 0)
|
||||
return()
|
||||
values$A <- values$A - 1
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsB), 4)
|
||||
})
|
||||
|
||||
test_that("Writing then reading value is not circular", {
|
||||
|
||||
values <- reactiveValues(A=3)
|
||||
funcB <- reactive({
|
||||
values$A <- isolate(values$A) - 1
|
||||
values$A
|
||||
})
|
||||
|
||||
obsC <- observe({
|
||||
funcB()
|
||||
})
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 1)
|
||||
|
||||
values$A <- 10
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(obsC), 2)
|
||||
})
|
||||
|
||||
test_that("names() and reactiveValuesToList()", {
|
||||
|
||||
values <- reactiveValues(A=1, .B=2)
|
||||
|
||||
# Dependent on names
|
||||
depNames <- observe({
|
||||
names(values)
|
||||
})
|
||||
|
||||
# Dependent on all non-hidden objects
|
||||
depValues <- observe({
|
||||
reactiveValuesToList(values)
|
||||
})
|
||||
|
||||
# Dependent on all objects, including hidden
|
||||
depAllValues <- observe({
|
||||
reactiveValuesToList(values, all.names = TRUE)
|
||||
})
|
||||
|
||||
# names() returns all names
|
||||
expect_equal(sort(isolate(names(values))), sort(c(".B", "A")))
|
||||
# Assigning names fails
|
||||
expect_error(isolate(names(v) <- c('x', 'y')))
|
||||
|
||||
expect_equal(isolate(reactiveValuesToList(values)), list(A=1))
|
||||
expect_equal(isolate(reactiveValuesToList(values, all.names=TRUE)), list(A=1, .B=2))
|
||||
|
||||
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 1)
|
||||
expect_equal(execCount(depValues), 1)
|
||||
expect_equal(execCount(depAllValues), 1)
|
||||
|
||||
# Update existing variable
|
||||
values$A <- 2
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 1)
|
||||
expect_equal(execCount(depValues), 2)
|
||||
expect_equal(execCount(depAllValues), 2)
|
||||
|
||||
# Update existing hidden variable
|
||||
values$.B <- 3
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 1)
|
||||
expect_equal(execCount(depValues), 2)
|
||||
expect_equal(execCount(depAllValues), 3)
|
||||
|
||||
# Add new variable
|
||||
values$C <- 1
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 2)
|
||||
expect_equal(execCount(depValues), 3)
|
||||
expect_equal(execCount(depAllValues), 4)
|
||||
|
||||
# Add new hidden variable
|
||||
values$.D <- 1
|
||||
flushReact()
|
||||
expect_equal(execCount(depNames), 3)
|
||||
expect_equal(execCount(depValues), 3)
|
||||
expect_equal(execCount(depAllValues), 5)
|
||||
})
|
||||
|
||||
test_that("Observer pausing works", {
|
||||
values <- reactiveValues(a=1)
|
||||
|
||||
funcA <- reactive({
|
||||
values$a
|
||||
})
|
||||
|
||||
obsB <- observe({
|
||||
funcA()
|
||||
})
|
||||
|
||||
# Important: suspend() only affects observer at invalidation time
|
||||
|
||||
# Observers are invalidated at creation time, so it will run once regardless
|
||||
# of being suspended
|
||||
obsB$suspend()
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(obsB), 1)
|
||||
|
||||
# When resuming, if nothing changed, don't do anything
|
||||
obsB$resume()
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(obsB), 1)
|
||||
|
||||
# Make sure suspended observers do not flush, but do invalidate
|
||||
obsB_invalidated <- FALSE
|
||||
obsB$onInvalidate(function() {obsB_invalidated <<- TRUE})
|
||||
obsB$suspend()
|
||||
values$a <- 2
|
||||
flushReact()
|
||||
expect_equal(obsB_invalidated, TRUE)
|
||||
expect_equal(execCount(funcA), 1)
|
||||
expect_equal(execCount(obsB), 1)
|
||||
|
||||
obsB$resume()
|
||||
values$a <- 2.5
|
||||
obsB$suspend()
|
||||
flushReact()
|
||||
expect_equal(execCount(funcA), 2)
|
||||
expect_equal(execCount(obsB), 2)
|
||||
|
||||
values$a <- 3
|
||||
flushReact()
|
||||
|
||||
expect_equal(execCount(funcA), 2)
|
||||
expect_equal(execCount(obsB), 2)
|
||||
|
||||
# If onInvalidate() is added _after_ obsB is suspended and the values$a
|
||||
# changes, then it shouldn't get run (onInvalidate runs on invalidation, not
|
||||
# on flush)
|
||||
values$a <- 4
|
||||
obsB_invalidated2 <- FALSE
|
||||
obsB$onInvalidate(function() {obsB_invalidated2 <<- TRUE})
|
||||
obsB$resume()
|
||||
flushReact()
|
||||
|
||||
expect_equal(execCount(funcA), 3)
|
||||
expect_equal(execCount(obsB), 3)
|
||||
expect_equal(obsB_invalidated2, FALSE)
|
||||
})
|
||||
|
||||
test_that("suspended/resumed observers run at most once", {
|
||||
|
||||
values <- reactiveValues(A=1)
|
||||
obs <- observe(function() {
|
||||
values$A
|
||||
})
|
||||
expect_equal(execCount(obs), 0)
|
||||
|
||||
# First flush should run obs once
|
||||
flushReact()
|
||||
expect_equal(execCount(obs), 1)
|
||||
|
||||
# Modify the dependency at each stage of suspend/resume/flush should still
|
||||
# only result in one run of obs()
|
||||
values$A <- 2
|
||||
obs$suspend()
|
||||
values$A <- 3
|
||||
obs$resume()
|
||||
values$A <- 4
|
||||
flushReact()
|
||||
expect_equal(execCount(obs), 2)
|
||||
|
||||
})
|
||||
|
||||
|
||||
test_that("reactive() accepts quoted and unquoted expressions", {
|
||||
vals <- reactiveValues(A=1)
|
||||
|
||||
# Unquoted expression, with curly braces
|
||||
fun <- reactive({ vals$A + 1 })
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
# Unquoted expression, no curly braces
|
||||
fun <- reactive(vals$A + 1)
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
# Quoted expression
|
||||
fun <- reactive(quote(vals$A + 1), quoted = TRUE)
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
# Quoted expression, saved in a variable
|
||||
q_expr <- quote(vals$A + 1)
|
||||
fun <- reactive(q_expr, quoted = TRUE)
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
# If function is used, work, but print message
|
||||
expect_message(fun <- reactive(function() { vals$A + 1 }))
|
||||
expect_equal(isolate(fun()), 2)
|
||||
|
||||
|
||||
# Check that environment is correct - parent environment should be this one
|
||||
this_env <- environment()
|
||||
fun <- reactive(environment())
|
||||
expect_identical(isolate(parent.env(fun())), this_env)
|
||||
|
||||
# Sanity check: environment structure for a reactive() should be the same as for
|
||||
# a normal function
|
||||
fun <- function() environment()
|
||||
expect_identical(parent.env(fun()), this_env)
|
||||
})
|
||||
|
||||
test_that("observe() accepts quoted and unquoted expressions", {
|
||||
vals <- reactiveValues(A=0)
|
||||
valB <- 0
|
||||
|
||||
# Unquoted expression, with curly braces
|
||||
observe({ valB <<- vals$A + 1})
|
||||
flushReact()
|
||||
expect_equal(valB, 1)
|
||||
|
||||
# Unquoted expression, no curly braces
|
||||
observe({ valB <<- vals$A + 2})
|
||||
flushReact()
|
||||
expect_equal(valB, 2)
|
||||
|
||||
# Quoted expression
|
||||
observe(quote(valB <<- vals$A + 3), quoted = TRUE)
|
||||
flushReact()
|
||||
expect_equal(valB, 3)
|
||||
|
||||
# Quoted expression, saved in a variable
|
||||
q_expr <- quote(valB <<- vals$A + 4)
|
||||
fun <- observe(q_expr, quoted = TRUE)
|
||||
flushReact()
|
||||
expect_equal(valB, 4)
|
||||
|
||||
# If function is used, work, but print message
|
||||
expect_message(observe(function() { valB <<- vals$A + 5 }))
|
||||
flushReact()
|
||||
expect_equal(valB, 5)
|
||||
|
||||
|
||||
# Check that environment is correct - parent environment should be this one
|
||||
this_env <- environment()
|
||||
inside_env <- NULL
|
||||
fun <- observe(inside_env <<- environment())
|
||||
flushReact()
|
||||
expect_identical(parent.env(inside_env), this_env)
|
||||
})
|
||||
23
inst/tests/test-tags.r
Normal file
23
inst/tests/test-tags.r
Normal file
@@ -0,0 +1,23 @@
|
||||
context("tags")
|
||||
|
||||
test_that("Basic tag writing works", {
|
||||
expect_equal(as.character(tagList("hi")), HTML("hi"))
|
||||
expect_equal(
|
||||
as.character(tagList("one", "two", tagList("three"))),
|
||||
HTML("one\ntwo\nthree"))
|
||||
expect_equal(
|
||||
as.character(tags$b("one")),
|
||||
HTML("<b>one</b>"))
|
||||
expect_equal(
|
||||
as.character(tags$b("one", "two")),
|
||||
HTML("<b>\n one\n two\n</b>"))
|
||||
expect_equal(
|
||||
as.character(tagList(list("one"))),
|
||||
HTML("one"))
|
||||
expect_equal(
|
||||
as.character(tagList(list(tagList("one")))),
|
||||
HTML("one"))
|
||||
expect_equal(
|
||||
as.character(tagList(tags$br(), "one")),
|
||||
HTML("<br/>\none"))
|
||||
})
|
||||
56
inst/tests/test-text.R
Normal file
56
inst/tests/test-text.R
Normal file
@@ -0,0 +1,56 @@
|
||||
context("text")
|
||||
|
||||
test_that("renderPrint and renderText behavior is correct", {
|
||||
expect_equal(isolate(renderPrint({ "foo" })()),
|
||||
'[1] "foo"')
|
||||
expect_equal(isolate(renderPrint({ invisible("foo") })()),
|
||||
'')
|
||||
expect_equal(isolate(renderPrint({ print("foo"); "bar"})()),
|
||||
'[1] "foo"\n[1] "bar"')
|
||||
expect_equal(isolate(renderPrint({ NULL })()),
|
||||
'NULL')
|
||||
expect_equal(isolate(renderPrint({ invisible() })()),
|
||||
'')
|
||||
expect_equal(isolate(renderPrint({ 1:5 })()),
|
||||
'[1] 1 2 3 4 5')
|
||||
|
||||
expect_equal(isolate(renderText({ "foo" })()),
|
||||
'foo')
|
||||
expect_equal(isolate(renderText({ invisible("foo") })()),
|
||||
'foo')
|
||||
# Capture the print output so it's not shown on console during test, and
|
||||
# also check that it is correct
|
||||
print_out <- capture.output(ret <- isolate(renderText({ print("foo"); "bar"})()))
|
||||
expect_equal(ret, 'bar')
|
||||
expect_equal(print_out, '[1] "foo"')
|
||||
expect_equal(isolate(renderText({ NULL })()),
|
||||
'')
|
||||
expect_equal(isolate(renderText({ invisible() })()),
|
||||
'')
|
||||
expect_equal(isolate(renderText({ 1:5 })()),
|
||||
'1 2 3 4 5')
|
||||
})
|
||||
|
||||
test_that("reactive functions save visibility state", {
|
||||
# Call each function twice - should be no change in state with second call
|
||||
|
||||
# invisible NULL
|
||||
f <- reactive({ invisible() })
|
||||
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=FALSE))
|
||||
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=FALSE))
|
||||
|
||||
# visible NULL
|
||||
f <- reactive({ NULL })
|
||||
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=TRUE))
|
||||
expect_identical(withVisible(isolate(f())), list(value=NULL, visible=TRUE))
|
||||
|
||||
# invisible non-NULL value
|
||||
f <- reactive({ invisible(10)})
|
||||
expect_identical(withVisible(isolate(f())), list(value=10, visible=FALSE))
|
||||
expect_identical(withVisible(isolate(f())), list(value=10, visible=FALSE))
|
||||
|
||||
# visible non-NULL value
|
||||
f <- reactive({ 10 })
|
||||
expect_identical(withVisible(isolate(f())), list(value=10, visible=TRUE))
|
||||
expect_identical(withVisible(isolate(f())), list(value=10, visible=TRUE))
|
||||
})
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
var exports = window.Shiny = window.Shiny || {};
|
||||
|
||||
$(document).on('submit', 'form:not([action])', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
function randomId() {
|
||||
return Math.floor(0x100000000 + (Math.random() * 0xF00000000)).toString(16);
|
||||
}
|
||||
@@ -274,8 +278,13 @@
|
||||
this.lastSentValues[name] = jsonValue;
|
||||
this.target.setInput(name, value);
|
||||
};
|
||||
this.reset = function() {
|
||||
this.lastSentValues = {};
|
||||
this.reset = function(values) {
|
||||
values = values || {};
|
||||
var strValues = {};
|
||||
$.each(values, function(key, value) {
|
||||
strValues[key] = JSON.stringify(value);
|
||||
});
|
||||
this.lastSentValues = strValues;
|
||||
};
|
||||
}).call(InputNoResendDecorator.prototype);
|
||||
|
||||
@@ -1003,17 +1012,22 @@
|
||||
},
|
||||
getValue: function(el) {
|
||||
var numberVal = $(el).val();
|
||||
if (!isNaN(numberVal))
|
||||
if (/^\s*$/.test(numberVal)) // Return null if all whitespace
|
||||
return null;
|
||||
else if (!isNaN(numberVal)) // If valid Javascript number string, coerce to number
|
||||
return +numberVal;
|
||||
else
|
||||
return numberVal;
|
||||
return numberVal; // If other string like "1e6", send it unchanged
|
||||
},
|
||||
getType: function(el) {
|
||||
return "number"
|
||||
}
|
||||
});
|
||||
inputBindings.register(numberInputBinding, 'shiny.numberInput');
|
||||
|
||||
|
||||
var sliderInputBinding = {};
|
||||
$.extend(sliderInputBinding, numberInputBinding, {
|
||||
$.extend(sliderInputBinding, textInputBinding, {
|
||||
find: function(scope) {
|
||||
// Check if jslider plugin is loaded
|
||||
if (!$.fn.slider)
|
||||
@@ -1274,6 +1288,7 @@
|
||||
|
||||
// Send later in case DOM layout isn't final yet.
|
||||
setTimeout(sendPlotSize, 0);
|
||||
setTimeout(sendOutputHiddenState, 0);
|
||||
}
|
||||
|
||||
function unbindOutputs(scope) {
|
||||
@@ -1298,9 +1313,9 @@
|
||||
return $(el).val();
|
||||
}
|
||||
|
||||
var inputs = new InputNoResendDecorator(new InputBatchSender(shinyapp));
|
||||
var inputsRate = new InputRateDecorator(inputs);
|
||||
var inputsDefer = new InputDeferDecorator(inputs);
|
||||
var inputsNoResend = new InputNoResendDecorator(new InputBatchSender(shinyapp));
|
||||
var inputsRate = new InputRateDecorator(inputsNoResend);
|
||||
var inputsDefer = new InputDeferDecorator(inputsNoResend);
|
||||
|
||||
inputs = inputsRate;
|
||||
$('input[type="submit"], button[type="submit"]').each(function() {
|
||||
@@ -1368,7 +1383,7 @@
|
||||
var ratePolicy = binding.getRatePolicy();
|
||||
if (ratePolicy != null) {
|
||||
inputsRate.setRatePolicy(
|
||||
id,
|
||||
effectiveId,
|
||||
ratePolicy.policy,
|
||||
ratePolicy.delay);
|
||||
}
|
||||
@@ -1486,15 +1501,37 @@
|
||||
// The server needs to know the size of each plot output element, in case
|
||||
// the plot is auto-sizing
|
||||
$('.shiny-plot-output').each(function() {
|
||||
var width = this.offsetWidth;
|
||||
var height = this.offsetHeight;
|
||||
initialValues['.shinyout_' + this.id + '_width'] = width;
|
||||
initialValues['.shinyout_' + this.id + '_height'] = height;
|
||||
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
|
||||
initialValues['.shinyout_' + this.id + '_width'] = this.offsetWidth;
|
||||
initialValues['.shinyout_' + this.id + '_height'] = this.offsetHeight;
|
||||
}
|
||||
});
|
||||
function sendPlotSize() {
|
||||
$('.shiny-plot-output').each(function() {
|
||||
inputs.setInput('.shinyout_' + this.id + '_width', this.offsetWidth);
|
||||
inputs.setInput('.shinyout_' + this.id + '_height', this.offsetHeight);
|
||||
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
|
||||
inputs.setInput('.shinyout_' + this.id + '_width', this.offsetWidth);
|
||||
inputs.setInput('.shinyout_' + this.id + '_height', this.offsetHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set initial state of outputs to hidden, if needed
|
||||
$('.shiny-bound-output').each(function() {
|
||||
if (this.offsetWidth === 0 && this.offsetHeight === 0) {
|
||||
initialValues['.shinyout_' + this.id + '_hidden'] = true;
|
||||
} else {
|
||||
initialValues['.shinyout_' + this.id + '_hidden'] = false;
|
||||
}
|
||||
});
|
||||
// Send update when hidden state changes
|
||||
function sendOutputHiddenState() {
|
||||
$('.shiny-bound-output').each(function() {
|
||||
// Assume that the object is hidden when width and height are 0
|
||||
if (this.offsetWidth === 0 && this.offsetHeight === 0) {
|
||||
inputs.setInput('.shinyout_' + this.id + '_hidden', true);
|
||||
} else {
|
||||
inputs.setInput('.shinyout_' + this.id + '_hidden', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
// The size of each plot may change either because the browser window was
|
||||
@@ -1502,9 +1539,12 @@
|
||||
// of 0x0). It's OK to over-report sizes because the input pipeline will
|
||||
// filter out values that haven't changed.
|
||||
$(window).resize(debounce(500, sendPlotSize));
|
||||
$('body').on('shown.sendPlotSize hidden.sendPlotSize', '*', sendPlotSize);
|
||||
$('body').on('shown.sendPlotSize', '*', sendPlotSize);
|
||||
$('body').on('shown.sendOutputHiddenState hidden.sendOutputHiddenState', '*',
|
||||
sendOutputHiddenState);
|
||||
|
||||
// We've collected all the initial values--start the server process!
|
||||
inputsNoResend.reset(initialValues);
|
||||
shinyapp.connect(initialValues);
|
||||
} // function initShiny()
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
from this function.)}
|
||||
|
||||
\item{content}{A function that takes a single argument
|
||||
\code{con} that is a file connection opened in mode
|
||||
\code{wb}, and writes the content of the download into
|
||||
the connection. (Reactive values and functions may be
|
||||
used from this function.)}
|
||||
\code{file} that is a file path (string) of a nonexistent
|
||||
temp file, and writes the content to that file path.
|
||||
(Reactive values and functions may be used from this
|
||||
function.)}
|
||||
|
||||
\item{contentType}{A string of the download's
|
||||
\href{http://en.wikipedia.org/wiki/Internet_media_type}{content
|
||||
@@ -43,8 +43,8 @@ output$downloadData <- downloadHandler(
|
||||
filename = function() {
|
||||
paste('data-', Sys.Date(), '.csv', sep='')
|
||||
},
|
||||
content = function(con) {
|
||||
write.csv(data, con)
|
||||
content = function(file) {
|
||||
write.csv(data, file)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
63
man/exprToFunction.Rd
Normal file
63
man/exprToFunction.Rd
Normal file
@@ -0,0 +1,63 @@
|
||||
\name{exprToFunction}
|
||||
\alias{exprToFunction}
|
||||
\title{Convert an expression or quoted expression to a function}
|
||||
\usage{
|
||||
exprToFunction(expr, env = parent.frame(2),
|
||||
quoted = FALSE)
|
||||
}
|
||||
\arguments{
|
||||
\item{expr}{A quoted or unquoted expression, or a
|
||||
function.}
|
||||
|
||||
\item{env}{The desired environment for the function.
|
||||
Defaults to the calling environment two steps back.}
|
||||
|
||||
\item{quoted}{Is the expression quoted?}
|
||||
}
|
||||
\description{
|
||||
This is to be called from another function, because it
|
||||
will attempt to get an unquoted expression from two calls
|
||||
back.
|
||||
}
|
||||
\details{
|
||||
If expr is a quoted expression, then this just converts
|
||||
it to a function. If expr is a function, then this simply
|
||||
returns expr (and prints a deprecation message. If expr
|
||||
was a non-quoted expression from two calls back, then
|
||||
this will quote the original expression and convert it to
|
||||
a function.
|
||||
}
|
||||
\examples{
|
||||
# Example of a new renderer, similar to renderText
|
||||
# This is something that toolkit authors will do
|
||||
renderTriple <- function(expr, env=parent.frame(), quoted=FALSE) {
|
||||
# Convert expr to a function
|
||||
func <- shiny::exprToFunction(expr, env, quoted)
|
||||
|
||||
function() {
|
||||
value <- func()
|
||||
paste(rep(value, 3), collapse=", ")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Example of using the renderer.
|
||||
# This is something that app authors will do.
|
||||
values <- reactiveValues(A="text")
|
||||
|
||||
\dontrun{
|
||||
# Create an output object
|
||||
output$tripleA <- renderTriple({
|
||||
values$A
|
||||
})
|
||||
}
|
||||
|
||||
# At the R console, you can experiment with the renderer using isolate()
|
||||
tripleA <- renderTriple({
|
||||
values$A
|
||||
})
|
||||
|
||||
isolate(tripleA())
|
||||
# "text, text, text"
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
}
|
||||
\details{
|
||||
\code{uiOutput} is intended to be used with
|
||||
\code{reactiveUI} on the server side. It is currently
|
||||
just an alias for \code{htmlOutput}.
|
||||
\code{renderUI} on the server side. It is currently just
|
||||
an alias for \code{htmlOutput}.
|
||||
}
|
||||
\examples{
|
||||
htmlOutput("summary")
|
||||
|
||||
84
man/isolate.Rd
Normal file
84
man/isolate.Rd
Normal file
@@ -0,0 +1,84 @@
|
||||
\name{isolate}
|
||||
\alias{isolate}
|
||||
\title{Create a non-reactive scope for an expression}
|
||||
\usage{
|
||||
isolate(expr)
|
||||
}
|
||||
\arguments{
|
||||
\item{expr}{An expression that can access reactive values
|
||||
or expressions.}
|
||||
}
|
||||
\description{
|
||||
Executes the given expression in a scope where reactive
|
||||
values or expression can be read, but they cannot cause
|
||||
the reactive scope of the caller to be re-evaluated when
|
||||
they change.
|
||||
}
|
||||
\details{
|
||||
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
|
||||
expression's value.) The \code{isolate} function lets you
|
||||
read a reactive value or expression without establishing
|
||||
this relationship.
|
||||
|
||||
The expression given to \code{isolate()} is evaluated in
|
||||
the calling environment. This means that if you assign a
|
||||
variable inside the \code{isolate()}, its value will be
|
||||
visible outside of the \code{isolate()}. If you want to
|
||||
avoid this, you can use \code{\link{local}()} inside the
|
||||
\code{isolate()}.
|
||||
|
||||
This function can also be useful for calling reactive
|
||||
expression at the console, which can be useful for
|
||||
debugging. To do so, simply wrap the calls to the
|
||||
reactive expression with \code{isolate()}.
|
||||
}
|
||||
\examples{
|
||||
\dontrun{
|
||||
observe({
|
||||
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)
|
||||
})
|
||||
|
||||
observe({
|
||||
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)
|
||||
})
|
||||
|
||||
observe({
|
||||
x <- 1
|
||||
# x outside of isolate() is affected
|
||||
isolate(x <- 2)
|
||||
print(x) # 2
|
||||
|
||||
y <- 1
|
||||
# Use local() to avoid affecting calling environment
|
||||
isolate(local(y <- 2))
|
||||
print(y) # 1
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Can also use isolate to call reactive expressions from the R console
|
||||
values <- reactiveValues(A=1)
|
||||
fun <- reactive({ as.character(values$A) })
|
||||
isolate(fun())
|
||||
# "1"
|
||||
|
||||
# isolate also works if the reactive expression accesses values from the
|
||||
# input object, like input$x
|
||||
}
|
||||
|
||||
@@ -2,32 +2,67 @@
|
||||
\alias{observe}
|
||||
\title{Create a reactive observer}
|
||||
\usage{
|
||||
observe(func)
|
||||
observe(x, env = parent.frame(), quoted = FALSE,
|
||||
label = NULL, suspended = FALSE)
|
||||
}
|
||||
\arguments{
|
||||
\item{func}{The function to observe. It must not have any
|
||||
parameters. Any return value from this function will be
|
||||
ignored.}
|
||||
\item{x}{An expression (quoted or unquoted). Any return
|
||||
value will be ignored.}
|
||||
|
||||
\item{env}{The parent environment for the reactive
|
||||
expression. By default, this is the calling environment,
|
||||
the same as when defining an ordinary non-reactive
|
||||
expression.}
|
||||
|
||||
\item{quoted}{Is the expression quoted? By default, this
|
||||
is \code{FALSE}. This is useful when you want to use an
|
||||
expression that is stored in a variable; to do so, it
|
||||
must be quoted with `quote()`.}
|
||||
|
||||
\item{label}{A label for the observer, useful for
|
||||
debugging.}
|
||||
|
||||
\item{suspended}{If \code{TRUE}, start the observer in a
|
||||
suspended state. If \code{FALSE} (the default), start in
|
||||
a non-suspended state.}
|
||||
}
|
||||
\description{
|
||||
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
|
||||
Creates an observer from the given expression An observer
|
||||
is like a reactive expression in that it can read
|
||||
reactive values and call reactive expressions, and will
|
||||
automatically re-execute when those dependencies change.
|
||||
But unlike reactive functions, it doesn't yield a result
|
||||
But unlike reactive expression, 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).
|
||||
expressions. Thus, observers are only useful for their
|
||||
side effects (for example, performing I/O).
|
||||
}
|
||||
\details{
|
||||
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.
|
||||
Another contrast between reactive expressions and
|
||||
observers is their execution strategy. Reactive
|
||||
expressions 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.
|
||||
}
|
||||
\examples{
|
||||
values <- reactiveValues(A=1)
|
||||
|
||||
obsB <- observe({
|
||||
print(values$A + 1)
|
||||
})
|
||||
|
||||
# Can use quoted expressions
|
||||
obsC <- observe(quote({ print(values$A + 2) }), quoted = TRUE)
|
||||
|
||||
# To store expressions for later conversion to observe, use quote()
|
||||
expr_q <- quote({ print(values$A + 3) })
|
||||
obsD <- observe(expr_q, quoted = TRUE)
|
||||
|
||||
# In a normal Shiny app, the web client will trigger flush events. If you
|
||||
# are at the console, you can force a flush with flushReact()
|
||||
shiny:::flushReact()
|
||||
}
|
||||
|
||||
|
||||
36
man/outputOptions.Rd
Normal file
36
man/outputOptions.Rd
Normal file
@@ -0,0 +1,36 @@
|
||||
\name{outputOptions}
|
||||
\alias{outputOptions}
|
||||
\title{Set options for an output object.}
|
||||
\usage{
|
||||
outputOptions(x, name, ...)
|
||||
}
|
||||
\arguments{
|
||||
\item{x}{A shinyoutput object (typically \code{output}).}
|
||||
|
||||
\item{name}{The name of an output observer in the
|
||||
shinyoutput object.}
|
||||
|
||||
\item{...}{Options to set for the output observer.}
|
||||
}
|
||||
\description{
|
||||
These are the available options for an output object:
|
||||
\itemize{ \item suspendWhenHidden. When \code{TRUE} (the
|
||||
default), the output object will be suspended (not
|
||||
execute) when it is hidden on the web page. When
|
||||
\code{FALSE}, the output object will not suspend when
|
||||
hidden, and if it was already hidden and suspended, then
|
||||
it will resume immediately. }
|
||||
}
|
||||
\examples{
|
||||
\dontrun{
|
||||
# Get the list of options for all observers within output
|
||||
outputOptions(output)
|
||||
|
||||
# Disable suspend for output$myplot
|
||||
outputOptions(output, "myplot", suspendWhenHidden = FALSE)
|
||||
|
||||
# Get the list of options for output$myplot
|
||||
outputOptions(output, "myplot")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
\arguments{
|
||||
\item{outputId}{output variable to read the plot from}
|
||||
|
||||
\item{width}{Plot width}
|
||||
\item{width}{Plot width. Must be a valid CSS unit (like
|
||||
\code{"100\%"}, \code{"400px"}, \code{"auto"}) or a
|
||||
number, which will be coerced to a string and have
|
||||
\code{"px"} appended.}
|
||||
|
||||
\item{height}{Plot height}
|
||||
}
|
||||
@@ -15,7 +18,7 @@
|
||||
A plot output element that can be included in a panel
|
||||
}
|
||||
\description{
|
||||
Render a \link{reactivePlot} within an application page.
|
||||
Render a \link{renderPlot} within an application page.
|
||||
}
|
||||
\examples{
|
||||
# Show a plot of the generated distribution
|
||||
|
||||
@@ -1,35 +1,65 @@
|
||||
\name{reactive}
|
||||
\alias{reactive}
|
||||
\title{Create a Reactive Function}
|
||||
\title{Create a reactive expression}
|
||||
\usage{
|
||||
reactive(x)
|
||||
reactive(x, env = parent.frame(), quoted = FALSE,
|
||||
label = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{x}{The value or function to make reactive. The
|
||||
function must not have any parameters.}
|
||||
}
|
||||
\value{
|
||||
A reactive function. (Note that reactive functions can
|
||||
only be called from within other reactive functions.)
|
||||
\item{x}{An expression (quoted or unquoted).}
|
||||
|
||||
\item{env}{The parent environment for the reactive
|
||||
expression. By default, this is the calling environment,
|
||||
the same as when defining an ordinary non-reactive
|
||||
expression.}
|
||||
|
||||
\item{quoted}{Is the expression quoted? By default, this
|
||||
is \code{FALSE}. This is useful when you want to use an
|
||||
expression that is stored in a variable; to do so, it
|
||||
must be quoted with `quote()`.}
|
||||
|
||||
\item{label}{A label for the reactive expression, useful
|
||||
for debugging.}
|
||||
}
|
||||
\description{
|
||||
Wraps a normal function to create a reactive function.
|
||||
Conceptually, a reactive function is a function whose
|
||||
result will change over time.
|
||||
Wraps a normal expression to create a reactive
|
||||
expression. Conceptually, a reactive expression is a
|
||||
expression whose result will change over time.
|
||||
}
|
||||
\details{
|
||||
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.
|
||||
Reactive expressions are expressions that can read
|
||||
reactive values and call other reactive expressions.
|
||||
Whenever a reactive value changes, any reactive
|
||||
expressions that depended on it are marked as
|
||||
"invalidated" and will automatically re-execute if
|
||||
necessary. If a reactive expression is marked as
|
||||
invalidated, any other reactive expressions that recently
|
||||
called it are also marked as invalidated. In this way,
|
||||
invalidations ripple through the expressions that depend
|
||||
on each other.
|
||||
|
||||
See the
|
||||
\href{http://rstudio.github.com/shiny/tutorial/}{Shiny
|
||||
tutorial} for more information about reactive functions.
|
||||
tutorial} for more information about reactive
|
||||
expressions.
|
||||
}
|
||||
\examples{
|
||||
values <- reactiveValues(A=1)
|
||||
|
||||
reactiveB <- reactive({
|
||||
values$A + 1
|
||||
})
|
||||
|
||||
# Can use quoted expressions
|
||||
reactiveC <- reactive(quote({ values$A + 2 }), quoted = TRUE)
|
||||
|
||||
# To store expressions for later conversion to reactive, use quote()
|
||||
expr_q <- quote({ values$A + 3 })
|
||||
reactiveD <- reactive(expr_q, quoted = TRUE)
|
||||
|
||||
# View the values from the R console with isolate()
|
||||
isolate(reactiveB())
|
||||
isolate(reactiveC())
|
||||
isolate(reactiveD())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,19 @@
|
||||
\name{reactivePlot}
|
||||
\alias{reactivePlot}
|
||||
\title{Plot Output}
|
||||
\title{Plot output (deprecated)}
|
||||
\usage{
|
||||
reactivePlot(func, width = "auto", height = "auto", ...)
|
||||
}
|
||||
\arguments{
|
||||
\item{func}{A function that generates a plot.}
|
||||
\item{func}{A function.}
|
||||
|
||||
\item{width}{The width of the rendered plot, in pixels;
|
||||
or \code{'auto'} to use the \code{offsetWidth} of the
|
||||
HTML element that is bound to this plot. You can also
|
||||
pass in a function that returns the width in pixels or
|
||||
\code{'auto'}; in the body of the function you may
|
||||
reference reactive values and functions.}
|
||||
\item{width}{Width.}
|
||||
|
||||
\item{height}{The height of the rendered plot, in pixels;
|
||||
or \code{'auto'} to use the \code{offsetHeight} of the
|
||||
HTML element that is bound to this plot. You can also
|
||||
pass in a function that returns the width in pixels or
|
||||
\code{'auto'}; in the body of the function you may
|
||||
reference reactive values and functions.}
|
||||
\item{height}{Height.}
|
||||
|
||||
\item{...}{Arguments to be passed through to
|
||||
\code{\link[grDevices]{png}}. These can be used to set
|
||||
the width, height, background color, etc.}
|
||||
\item{...}{Other arguments to pass on.}
|
||||
}
|
||||
\description{
|
||||
Creates a reactive plot that is suitable for assigning to
|
||||
an \code{output} slot.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag should be \code{div} or
|
||||
\code{img} and have the CSS class name
|
||||
\code{shiny-plot-output}.
|
||||
See \code{\link{renderPlot}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,13 @@
|
||||
\name{reactivePrint}
|
||||
\alias{reactivePrint}
|
||||
\title{Printable Output}
|
||||
\title{Print output (deprecated)}
|
||||
\usage{
|
||||
reactivePrint(func)
|
||||
}
|
||||
\arguments{
|
||||
\item{func}{A function that returns a printable R
|
||||
object.}
|
||||
\item{func}{A function.}
|
||||
}
|
||||
\description{
|
||||
Makes a reactive version of the given function that also
|
||||
turns its printable result into a string. The reactive
|
||||
function is suitable for assigning to an \code{output}
|
||||
slot.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag can be anything (though
|
||||
\code{pre} is recommended if you need a monospace font
|
||||
and whitespace preserved) and should have the CSS class
|
||||
name \code{shiny-text-output}.
|
||||
|
||||
The result of executing \code{func} will be printed
|
||||
inside a \code{\link[utils]{capture.output}} call.
|
||||
See \code{\link{renderPrint}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
\name{reactiveTable}
|
||||
\alias{reactiveTable}
|
||||
\title{Table Output}
|
||||
\title{Table output (deprecated)}
|
||||
\usage{
|
||||
reactiveTable(func, ...)
|
||||
}
|
||||
\arguments{
|
||||
\item{func}{A function that returns an R object that can
|
||||
be used with \code{\link[xtable]{xtable}}.}
|
||||
\item{func}{A function.}
|
||||
|
||||
\item{...}{Arguments to be passed through to
|
||||
\code{\link[xtable]{xtable}} and
|
||||
\code{\link[xtable]{print.xtable}}.}
|
||||
\item{...}{Other arguments to pass on.}
|
||||
}
|
||||
\description{
|
||||
Creates a reactive table that is suitable for assigning
|
||||
to an \code{output} slot.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag should be \code{div}
|
||||
and have the CSS class name \code{shiny-html-output}.
|
||||
See \code{\link{renderTable}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,13 @@
|
||||
\name{reactiveText}
|
||||
\alias{reactiveText}
|
||||
\title{Text Output}
|
||||
\title{Text output (deprecated)}
|
||||
\usage{
|
||||
reactiveText(func)
|
||||
}
|
||||
\arguments{
|
||||
\item{func}{A function that returns an R object that can
|
||||
be used as an argument to \code{cat}.}
|
||||
\item{func}{A function.}
|
||||
}
|
||||
\description{
|
||||
Makes a reactive version of the given function that also
|
||||
uses \code{\link[base]{cat}} to turn its result into a
|
||||
single-element character vector.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag can be anything (though
|
||||
\code{pre} is recommended if you need a monospace font
|
||||
and whitespace preserved) and should have the CSS class
|
||||
name \code{shiny-text-output}.
|
||||
|
||||
The result of executing \code{func} will passed to
|
||||
\code{cat}, inside a \code{\link[utils]{capture.output}}
|
||||
call.
|
||||
See \code{\link{renderText}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
timers are triggered simply by the passage of time.
|
||||
}
|
||||
\details{
|
||||
\link[=reactive]{Reactive functions} and observers that
|
||||
\link[=reactive]{Reactive expressions} 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.
|
||||
|
||||
@@ -1,33 +1,13 @@
|
||||
\name{reactiveUI}
|
||||
\alias{reactiveUI}
|
||||
\title{UI Output}
|
||||
\title{UI output (deprecated)}
|
||||
\usage{
|
||||
reactiveUI(func)
|
||||
}
|
||||
\arguments{
|
||||
\item{func}{A function that returns a Shiny tag object,
|
||||
\code{\link{HTML}}, or a list of such objects.}
|
||||
\item{func}{A function.}
|
||||
}
|
||||
\description{
|
||||
\bold{Experimental feature.} Makes a reactive version of
|
||||
a function that generates HTML using the Shiny UI
|
||||
library.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag should be \code{div}
|
||||
and have the CSS class name \code{shiny-html-output} (or
|
||||
use \code{\link{uiOutput}}).
|
||||
}
|
||||
\examples{
|
||||
\dontrun{
|
||||
output$moreControls <- reactiveUI(function() {
|
||||
list(
|
||||
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
conditionalPanel
|
||||
See \code{\link{renderUI}}.
|
||||
}
|
||||
|
||||
|
||||
47
man/reactiveValues.Rd
Normal file
47
man/reactiveValues.Rd
Normal file
@@ -0,0 +1,47 @@
|
||||
\name{reactiveValues}
|
||||
\alias{reactiveValues}
|
||||
\title{Create an object for storing reactive values}
|
||||
\usage{
|
||||
reactiveValues(...)
|
||||
}
|
||||
\arguments{
|
||||
\item{...}{Objects that will be added to the
|
||||
reactivevalues object. All of these objects must be
|
||||
named.}
|
||||
}
|
||||
\description{
|
||||
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 expression 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)
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{isolate}}.
|
||||
}
|
||||
|
||||
33
man/reactiveValuesToList.Rd
Normal file
33
man/reactiveValuesToList.Rd
Normal file
@@ -0,0 +1,33 @@
|
||||
\name{reactiveValuesToList}
|
||||
\alias{reactiveValuesToList}
|
||||
\title{Convert a reactivevalues object to a list}
|
||||
\usage{
|
||||
reactiveValuesToList(x, all.names = FALSE)
|
||||
}
|
||||
\arguments{
|
||||
\item{x}{A reactivevalues object.}
|
||||
|
||||
\item{all.names}{If \code{TRUE}, include objects with a
|
||||
leading dot. If \code{FALSE} (the default) don't include
|
||||
those objects.}
|
||||
}
|
||||
\description{
|
||||
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}()}.
|
||||
}
|
||||
\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))
|
||||
}
|
||||
|
||||
56
man/renderPlot.Rd
Normal file
56
man/renderPlot.Rd
Normal file
@@ -0,0 +1,56 @@
|
||||
\name{renderPlot}
|
||||
\alias{renderPlot}
|
||||
\title{Plot Output}
|
||||
\usage{
|
||||
renderPlot(expr, width = "auto", height = "auto", ...,
|
||||
env = parent.frame(), quoted = FALSE, func = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{expr}{An expression that generates a plot.}
|
||||
|
||||
\item{width}{The width of the rendered plot, in pixels;
|
||||
or \code{'auto'} to use the \code{offsetWidth} of the
|
||||
HTML element that is bound to this plot. You can also
|
||||
pass in a function that returns the width in pixels or
|
||||
\code{'auto'}; in the body of the function you may
|
||||
reference reactive values and functions.}
|
||||
|
||||
\item{height}{The height of the rendered plot, in pixels;
|
||||
or \code{'auto'} to use the \code{offsetHeight} of the
|
||||
HTML element that is bound to this plot. You can also
|
||||
pass in a function that returns the width in pixels or
|
||||
\code{'auto'}; in the body of the function you may
|
||||
reference reactive values and functions.}
|
||||
|
||||
\item{...}{Arguments to be passed through to
|
||||
\code{\link[grDevices]{png}}. These can be used to set
|
||||
the width, height, background color, etc.}
|
||||
|
||||
\item{env}{The environment in which to evaluate
|
||||
\code{expr}.}
|
||||
|
||||
\item{quoted}{Is \code{expr} a quoted expression (with
|
||||
\code{quote()})? This is useful if you want to save an
|
||||
expression in a variable.}
|
||||
|
||||
\item{func}{A function that generates a plot (deprecated;
|
||||
use \code{expr} instead).}
|
||||
}
|
||||
\description{
|
||||
Renders a reactive plot that is suitable for assigning to
|
||||
an \code{output} slot.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag should be \code{div} or
|
||||
\code{img} and have the CSS class name
|
||||
\code{shiny-plot-output}.
|
||||
|
||||
For output, it will try to use the following devices, in
|
||||
this order: quartz (via \code{\link[grDevices]{png}}),
|
||||
then \code{\link[Cairo]{CairoPNG}}, and finally
|
||||
\code{\link[grDevices]{png}}. This is in order of quality
|
||||
of output. Notably, plain \code{png} output on Linux and
|
||||
Windows may not antialias some point shapes, resulting in
|
||||
poor quality output.
|
||||
}
|
||||
|
||||
112
man/renderPrint.Rd
Normal file
112
man/renderPrint.Rd
Normal file
@@ -0,0 +1,112 @@
|
||||
\name{renderPrint}
|
||||
\alias{renderPrint}
|
||||
\title{Printable Output}
|
||||
\usage{
|
||||
renderPrint(expr, env = parent.frame(), quoted = FALSE,
|
||||
func = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{expr}{An expression that may print output and/or
|
||||
return a printable R object.}
|
||||
|
||||
\item{env}{The environment in which to evaluate
|
||||
\code{expr}.}
|
||||
|
||||
\item{quoted}{Is \code{expr} a quoted expression (with
|
||||
\code{quote()})? This}
|
||||
|
||||
\item{func}{A function that may print output and/or
|
||||
return a printable R object (deprecated; use \code{expr}
|
||||
instead).}
|
||||
}
|
||||
\description{
|
||||
Makes a reactive version of the given function that
|
||||
captures any printed output, and also captures its
|
||||
printable result (unless \code{\link{invisible}}), into a
|
||||
string. The resulting function is suitable for assigning
|
||||
to an \code{output} slot.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag can be anything (though
|
||||
\code{pre} is recommended if you need a monospace font
|
||||
and whitespace preserved) and should have the CSS class
|
||||
name \code{shiny-text-output}.
|
||||
|
||||
The result of executing \code{func} will be printed
|
||||
inside a \code{\link[utils]{capture.output}} call.
|
||||
|
||||
Note that unlike most other Shiny output functions, if
|
||||
the given function returns \code{NULL} then \code{NULL}
|
||||
will actually be visible in the output. To display
|
||||
nothing, make your function return
|
||||
\code{\link{invisible}()}.
|
||||
}
|
||||
\examples{
|
||||
isolate({
|
||||
|
||||
# renderPrint captures any print output, converts it to a string, and
|
||||
# returns it
|
||||
visFun <- renderPrint({ "foo" })
|
||||
visFun()
|
||||
# '[1] "foo"'
|
||||
|
||||
invisFun <- renderPrint({ invisible("foo") })
|
||||
invisFun()
|
||||
# ''
|
||||
|
||||
multiprintFun <- renderPrint({
|
||||
print("foo");
|
||||
"bar"
|
||||
})
|
||||
multiprintFun()
|
||||
# '[1] "foo"\\n[1] "bar"'
|
||||
|
||||
nullFun <- renderPrint({ NULL })
|
||||
nullFun()
|
||||
# 'NULL'
|
||||
|
||||
invisNullFun <- renderPrint({ invisible(NULL) })
|
||||
invisNullFun()
|
||||
# ''
|
||||
|
||||
vecFun <- renderPrint({ 1:5 })
|
||||
vecFun()
|
||||
# '[1] 1 2 3 4 5'
|
||||
|
||||
|
||||
# Contrast with renderText, which takes the value returned from the function
|
||||
# and uses cat() to convert it to a string
|
||||
visFun <- renderText({ "foo" })
|
||||
visFun()
|
||||
# 'foo'
|
||||
|
||||
invisFun <- renderText({ invisible("foo") })
|
||||
invisFun()
|
||||
# 'foo'
|
||||
|
||||
multiprintFun <- renderText({
|
||||
print("foo");
|
||||
"bar"
|
||||
})
|
||||
multiprintFun()
|
||||
# 'bar'
|
||||
|
||||
nullFun <- renderText({ NULL })
|
||||
nullFun()
|
||||
# ''
|
||||
|
||||
invisNullFun <- renderText({ invisible(NULL) })
|
||||
invisNullFun()
|
||||
# ''
|
||||
|
||||
vecFun <- renderText({ 1:5 })
|
||||
vecFun()
|
||||
# '1 2 3 4 5'
|
||||
|
||||
})
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{renderText}} for displaying the value
|
||||
returned from a function, instead of the printed output.
|
||||
}
|
||||
|
||||
35
man/renderTable.Rd
Normal file
35
man/renderTable.Rd
Normal file
@@ -0,0 +1,35 @@
|
||||
\name{renderTable}
|
||||
\alias{renderTable}
|
||||
\title{Table Output}
|
||||
\usage{
|
||||
renderTable(expr, ..., env = parent.frame(),
|
||||
quoted = FALSE, func = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{expr}{An expression that returns an R object that
|
||||
can be used with \code{\link[xtable]{xtable}}.}
|
||||
|
||||
\item{...}{Arguments to be passed through to
|
||||
\code{\link[xtable]{xtable}} and
|
||||
\code{\link[xtable]{print.xtable}}.}
|
||||
|
||||
\item{env}{The environment in which to evaluate
|
||||
\code{expr}.}
|
||||
|
||||
\item{quoted}{Is \code{expr} a quoted expression (with
|
||||
\code{quote()})? This is useful if you want to save an
|
||||
expression in a variable.}
|
||||
|
||||
\item{func}{A function that returns an R object that can
|
||||
be used with \code{\link[xtable]{xtable}} (deprecated;
|
||||
use \code{expr} instead).}
|
||||
}
|
||||
\description{
|
||||
Creates a reactive table that is suitable for assigning
|
||||
to an \code{output} slot.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag should be \code{div}
|
||||
and have the CSS class name \code{shiny-html-output}.
|
||||
}
|
||||
|
||||
106
man/renderText.Rd
Normal file
106
man/renderText.Rd
Normal file
@@ -0,0 +1,106 @@
|
||||
\name{renderText}
|
||||
\alias{renderText}
|
||||
\title{Text Output}
|
||||
\usage{
|
||||
renderText(expr, env = parent.frame(), quoted = FALSE,
|
||||
func = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{expr}{An expression that returns an R object that
|
||||
can be used as an argument to \code{cat}.}
|
||||
|
||||
\item{env}{The environment in which to evaluate
|
||||
\code{expr}.}
|
||||
|
||||
\item{quoted}{Is \code{expr} a quoted expression (with
|
||||
\code{quote()})? This is useful if you want to save an
|
||||
expression in a variable.}
|
||||
|
||||
\item{func}{A function that returns an R object that can
|
||||
be used as an argument to \code{cat}.(deprecated; use
|
||||
\code{expr} instead).}
|
||||
}
|
||||
\description{
|
||||
Makes a reactive version of the given function that also
|
||||
uses \code{\link[base]{cat}} to turn its result into a
|
||||
single-element character vector.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag can be anything (though
|
||||
\code{pre} is recommended if you need a monospace font
|
||||
and whitespace preserved) and should have the CSS class
|
||||
name \code{shiny-text-output}.
|
||||
|
||||
The result of executing \code{func} will passed to
|
||||
\code{cat}, inside a \code{\link[utils]{capture.output}}
|
||||
call.
|
||||
}
|
||||
\examples{
|
||||
isolate({
|
||||
|
||||
# renderPrint captures any print output, converts it to a string, and
|
||||
# returns it
|
||||
visFun <- renderPrint({ "foo" })
|
||||
visFun()
|
||||
# '[1] "foo"'
|
||||
|
||||
invisFun <- renderPrint({ invisible("foo") })
|
||||
invisFun()
|
||||
# ''
|
||||
|
||||
multiprintFun <- renderPrint({
|
||||
print("foo");
|
||||
"bar"
|
||||
})
|
||||
multiprintFun()
|
||||
# '[1] "foo"\\n[1] "bar"'
|
||||
|
||||
nullFun <- renderPrint({ NULL })
|
||||
nullFun()
|
||||
# 'NULL'
|
||||
|
||||
invisNullFun <- renderPrint({ invisible(NULL) })
|
||||
invisNullFun()
|
||||
# ''
|
||||
|
||||
vecFun <- renderPrint({ 1:5 })
|
||||
vecFun()
|
||||
# '[1] 1 2 3 4 5'
|
||||
|
||||
|
||||
# Contrast with renderText, which takes the value returned from the function
|
||||
# and uses cat() to convert it to a string
|
||||
visFun <- renderText({ "foo" })
|
||||
visFun()
|
||||
# 'foo'
|
||||
|
||||
invisFun <- renderText({ invisible("foo") })
|
||||
invisFun()
|
||||
# 'foo'
|
||||
|
||||
multiprintFun <- renderText({
|
||||
print("foo");
|
||||
"bar"
|
||||
})
|
||||
multiprintFun()
|
||||
# 'bar'
|
||||
|
||||
nullFun <- renderText({ NULL })
|
||||
nullFun()
|
||||
# ''
|
||||
|
||||
invisNullFun <- renderText({ invisible(NULL) })
|
||||
invisNullFun()
|
||||
# ''
|
||||
|
||||
vecFun <- renderText({ 1:5 })
|
||||
vecFun()
|
||||
# '1 2 3 4 5'
|
||||
|
||||
})
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{renderPrint}} for capturing the print output
|
||||
of a function, rather than the returned text value.
|
||||
}
|
||||
|
||||
45
man/renderUI.Rd
Normal file
45
man/renderUI.Rd
Normal file
@@ -0,0 +1,45 @@
|
||||
\name{renderUI}
|
||||
\alias{renderUI}
|
||||
\title{UI Output}
|
||||
\usage{
|
||||
renderUI(expr, env = parent.frame(), quoted = FALSE,
|
||||
func = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{expr}{An expression that returns a Shiny tag
|
||||
object, \code{\link{HTML}}, or a list of such objects.}
|
||||
|
||||
\item{env}{The environment in which to evaluate
|
||||
\code{expr}.}
|
||||
|
||||
\item{quoted}{Is \code{expr} a quoted expression (with
|
||||
\code{quote()})? This is useful if you want to save an
|
||||
expression in a variable.}
|
||||
|
||||
\item{func}{A function that returns a Shiny tag object,
|
||||
\code{\link{HTML}}, or a list of such objects
|
||||
(deprecated; use \code{expr} instead).}
|
||||
}
|
||||
\description{
|
||||
\bold{Experimental feature.} Makes a reactive version of
|
||||
a function that generates HTML using the Shiny UI
|
||||
library.
|
||||
}
|
||||
\details{
|
||||
The corresponding HTML output tag should be \code{div}
|
||||
and have the CSS class name \code{shiny-html-output} (or
|
||||
use \code{\link{uiOutput}}).
|
||||
}
|
||||
\examples{
|
||||
\dontrun{
|
||||
output$moreControls <- renderUI({
|
||||
list(
|
||||
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
conditionalPanel
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
}
|
||||
\arguments{
|
||||
\item{gist}{The identifier of the gist. For example, if
|
||||
the gist is https://gist.github.com/3239667, then
|
||||
the gist is https://gist.github.com/jcheng5/3239667, then
|
||||
\code{3239667}, \code{'3239667'}, and
|
||||
\code{'https://gist.github.com/3239667'} are all valid
|
||||
values.}
|
||||
\code{'https://gist.github.com/jcheng5/3239667'} are all
|
||||
valid values.}
|
||||
|
||||
\item{port}{The TCP port that the application should
|
||||
listen on. Defaults to port 8100.}
|
||||
@@ -23,4 +23,13 @@
|
||||
Download and launch a Shiny application that is hosted on
|
||||
GitHub as a gist.
|
||||
}
|
||||
\examples{
|
||||
\dontrun{
|
||||
runGist(3239667)
|
||||
runGist("https://gist.github.com/jcheng5/3239667")
|
||||
|
||||
# Old URL format without username
|
||||
runGist("https://gist.github.com/3239667")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
41
man/runGitHub.Rd
Normal file
41
man/runGitHub.Rd
Normal file
@@ -0,0 +1,41 @@
|
||||
\name{runGitHub}
|
||||
\alias{runGitHub}
|
||||
\title{Run a Shiny application from a GitHub repository}
|
||||
\usage{
|
||||
runGitHub(repo, username = getOption("github.user"),
|
||||
ref = "master", subdir = NULL, port = 8100,
|
||||
launch.browser = getOption("shiny.launch.browser", interactive()))
|
||||
}
|
||||
\arguments{
|
||||
\item{repo}{Name of the repository}
|
||||
|
||||
\item{username}{GitHub username}
|
||||
|
||||
\item{ref}{Desired git reference. Could be a commit, tag,
|
||||
or branch name. Defaults to \code{"master"}.}
|
||||
|
||||
\item{subdir}{A subdirectory in the repository that
|
||||
contains the app. By default, this function will run an
|
||||
app from the top level of the repo, but you can use a
|
||||
path such as `\code{"inst/shinyapp"}.}
|
||||
|
||||
\item{port}{The TCP port that the application should
|
||||
listen on. Defaults to port 8100.}
|
||||
|
||||
\item{launch.browser}{If true, the system's default web
|
||||
browser will be launched automatically after the app is
|
||||
started. Defaults to true in interactive sessions only.}
|
||||
}
|
||||
\description{
|
||||
Download and launch a Shiny application that is hosted in
|
||||
a GitHub repository.
|
||||
}
|
||||
\examples{
|
||||
\dontrun{
|
||||
runGitHub("shiny_example", "rstudio")
|
||||
|
||||
# Can run an app from a subdirectory in the repo
|
||||
runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
|
||||
}
|
||||
}
|
||||
|
||||
44
man/runUrl.Rd
Normal file
44
man/runUrl.Rd
Normal file
@@ -0,0 +1,44 @@
|
||||
\name{runUrl}
|
||||
\alias{runUrl}
|
||||
\title{Run a Shiny application from a URL}
|
||||
\usage{
|
||||
runUrl(url, filetype = NULL, subdir = NULL, port = 8100,
|
||||
launch.browser = getOption("shiny.launch.browser", interactive()))
|
||||
}
|
||||
\arguments{
|
||||
\item{url}{URL of the application.}
|
||||
|
||||
\item{filetype}{The file type (\code{".zip"},
|
||||
\code{".tar"}, or \code{".tar.gz"}. Defaults to the file
|
||||
extension taken from the url.}
|
||||
|
||||
\item{subdir}{A subdirectory in the repository that
|
||||
contains the app. By default, this function will run an
|
||||
app from the top level of the repo, but you can use a
|
||||
path such as `\code{"inst/shinyapp"}.}
|
||||
|
||||
\item{port}{The TCP port that the application should
|
||||
listen on. Defaults to port 8100.}
|
||||
|
||||
\item{launch.browser}{If true, the system's default web
|
||||
browser will be launched automatically after the app is
|
||||
started. Defaults to true in interactive sessions only.}
|
||||
}
|
||||
\description{
|
||||
Download and launch a Shiny application that is hosted at
|
||||
a downloadable URL. The Shiny application must be saved
|
||||
in a .zip, .tar, or .tar.gz file. The Shiny application
|
||||
files must be contained in a subdirectory in the archive.
|
||||
For example, the files might be \code{myapp/server.r} and
|
||||
\code{myapp/ui.r}.
|
||||
}
|
||||
\examples{
|
||||
\dontrun{
|
||||
runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
|
||||
# Can run an app from a subdirectory in the archive
|
||||
runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
subdir = "inst/shinyapp/")
|
||||
}
|
||||
}
|
||||
|
||||
20
man/shinyDeprecated.Rd
Normal file
20
man/shinyDeprecated.Rd
Normal file
@@ -0,0 +1,20 @@
|
||||
\name{shinyDeprecated}
|
||||
\alias{shinyDeprecated}
|
||||
\title{Print message for deprecated functions in Shiny}
|
||||
\usage{
|
||||
shinyDeprecated(new = NULL, msg = NULL,
|
||||
old = as.character(sys.call(sys.parent()))[1L])
|
||||
}
|
||||
\arguments{
|
||||
\item{new}{Name of replacement function.}
|
||||
|
||||
\item{msg}{Message to print. If used, this will override
|
||||
the default message.}
|
||||
|
||||
\item{old}{Name of deprecated function.}
|
||||
}
|
||||
\description{
|
||||
To disable these messages, use
|
||||
\code{options(shiny.deprecation.messages=FALSE)}.
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
# A very simple Shiny app that takes a message from the user
|
||||
# and outputs an uppercase version of it.
|
||||
shinyServer(function(input, output) {
|
||||
output$uppercase <- reactiveText(function() {
|
||||
output$uppercase <- renderText({
|
||||
toupper(input$message)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
A table output element that can be included in a panel
|
||||
}
|
||||
\description{
|
||||
Render a \link{reactiveTable} within an application page.
|
||||
Render a \link{renderTable} within an application page.
|
||||
}
|
||||
\examples{
|
||||
mainPanel(
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
}
|
||||
\details{
|
||||
Text is HTML-escaped prior to rendering. This element is
|
||||
often used to dispaly \link{reactiveText} output
|
||||
variables.
|
||||
often used to display \link{renderText} output variables.
|
||||
}
|
||||
\examples{
|
||||
h3(textOutput("caption"))
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
\details{
|
||||
Text is HTML-escaped prior to rendering. This element is
|
||||
often used with the \link{reactivePrint} function to
|
||||
often used with the \link{renderPrint} function to
|
||||
preserve fixed-width formatting of printed objects.
|
||||
}
|
||||
\examples{
|
||||
|
||||
62
res/text-example.R
Normal file
62
res/text-example.R
Normal file
@@ -0,0 +1,62 @@
|
||||
isolate({
|
||||
|
||||
# renderPrint captures any print output, converts it to a string, and
|
||||
# returns it
|
||||
visFun <- renderPrint({ "foo" })
|
||||
visFun()
|
||||
# '[1] "foo"'
|
||||
|
||||
invisFun <- renderPrint({ invisible("foo") })
|
||||
invisFun()
|
||||
# ''
|
||||
|
||||
multiprintFun <- renderPrint({
|
||||
print("foo");
|
||||
"bar"
|
||||
})
|
||||
multiprintFun()
|
||||
# '[1] "foo"\n[1] "bar"'
|
||||
|
||||
nullFun <- renderPrint({ NULL })
|
||||
nullFun()
|
||||
# 'NULL'
|
||||
|
||||
invisNullFun <- renderPrint({ invisible(NULL) })
|
||||
invisNullFun()
|
||||
# ''
|
||||
|
||||
vecFun <- renderPrint({ 1:5 })
|
||||
vecFun()
|
||||
# '[1] 1 2 3 4 5'
|
||||
|
||||
|
||||
# Contrast with renderText, which takes the value returned from the function
|
||||
# and uses cat() to convert it to a string
|
||||
visFun <- renderText({ "foo" })
|
||||
visFun()
|
||||
# 'foo'
|
||||
|
||||
invisFun <- renderText({ invisible("foo") })
|
||||
invisFun()
|
||||
# 'foo'
|
||||
|
||||
multiprintFun <- renderText({
|
||||
print("foo");
|
||||
"bar"
|
||||
})
|
||||
multiprintFun()
|
||||
# 'bar'
|
||||
|
||||
nullFun <- renderText({ NULL })
|
||||
nullFun()
|
||||
# ''
|
||||
|
||||
invisNullFun <- renderText({ invisible(NULL) })
|
||||
invisNullFun()
|
||||
# ''
|
||||
|
||||
vecFun <- renderText({ 1:5 })
|
||||
vecFun()
|
||||
# '1 2 3 4 5'
|
||||
|
||||
})
|
||||
4
tests/test-all.R
Normal file
4
tests/test-all.R
Normal file
@@ -0,0 +1,4 @@
|
||||
library(testthat)
|
||||
library(shiny)
|
||||
|
||||
test_package("shiny")
|
||||
Reference in New Issue
Block a user