Compare commits

...

74 Commits

Author SHA1 Message Date
Winston Chang
f455706d7c Bump version to 0.3.1 2013-01-29 21:16:44 -05:00
Winston Chang
23e9672476 Update NEWS 2013-01-26 13:16:42 -06:00
Winston Chang
36f992f95f Add [[<-.shinyoutput operator 2013-01-26 13:08:40 -06:00
Joe Cheng
b2c6d526ab Merge pull request #92 from wch/fix-download
Use correct default label for contexts. Fixes #91
2013-01-25 13:17:04 -08:00
Winston Chang
fe1e833677 Use correct default label for contexts. Fixes #91
NULL apparently is not a valid value for a field in a reference class.
2013-01-25 14:57:05 -06:00
Joe Cheng
8df1b9e8e5 Merge pull request #85 from jcheng5/flush-all
Flush all shinyapp instances
2013-01-25 08:52:51 -08:00
Joe Cheng
38b0f71b01 Merge pull request #89 from wch/reactive-invisible
Store visibility state of functions called from Observable
2013-01-25 00:47:42 -08:00
Winston Chang
29d2f115f8 Better reactiveText test 2013-01-24 23:10:02 -06:00
Winston Chang
0f677b4891 Add tests for reactive function return visibility 2013-01-24 22:45:07 -06:00
Winston Chang
2f7dd04168 Observable: save visibility state of function 2013-01-24 21:57:49 -06:00
Winston Chang
ed3b667985 Remove unneeded eval.parent 2013-01-24 21:38:25 -06:00
Joe Cheng
6ae1d8c158 Flush all shinyapp instances
Allows reactivity to affect all app instances at once.
(It already does but the outputs don't currently update)
2013-01-24 13:48:05 -08:00
Winston Chang
404bced97b Bump version to .99 for development 2013-01-24 13:58:58 -06:00
Winston Chang
5af49c8a82 Bump version and update NEWS 2013-01-23 14:54:39 -06:00
Winston Chang
85aa98e8e2 Fixes for R CMD check 2013-01-23 14:30:11 -06:00
Joe Cheng
330d102f62 Fix test on Linux (sort locale) 2013-01-23 12:17:45 -08:00
Joe Cheng
32b33a7910 Add res dir to .Rbuildignore 2013-01-23 12:13:40 -08:00
Joe Cheng
17c6a0f28a Merge branch 'reactivePrint-invisible'
Conflicts:
	man/plotOutput.Rd
2013-01-23 12:09:53 -08:00
Joe Cheng
7341eed1cf Merge pull request #80 from wch/run-github
Add functions runGithub and runUrl
2013-01-23 12:06:46 -08:00
Joe Cheng
ff99fbfbc9 Fix #64: Hitting Enter in textbox causes form submit 2013-01-23 11:54:06 -08:00
Winston Chang
9f67fdc771 Re-document 2013-01-23 13:44:18 -06:00
Winston Chang
521143a16b Add subdir argument for runGitHub and runUrl 2013-01-23 13:44:17 -06:00
Winston Chang
2622a25b12 Add runGitHub and runUrl functions 2013-01-23 13:44:17 -06:00
Joe Cheng
a91e925221 Remove failure comment 2013-01-23 11:33:06 -08:00
Joe Cheng
6c3289d5a5 Documentation and examples for reactivePrint/reactiveText 2013-01-23 11:32:13 -08:00
Joe Cheng
988a91ac06 reactiveText shouldn't capture print output 2013-01-23 11:31:51 -08:00
Winston Chang
aa7c913e9a Escape percent sign in documentation 2013-01-23 09:42:08 -06:00
Joe Cheng
56db9feaa4 reactivePrint should not display invisibles 2013-01-22 23:36:51 -08:00
Winston Chang
5ace0f13c9 Move validateCssUnit to separate function 2013-01-23 00:02:16 -06:00
Winston Chang
076e6c9479 Re-roxgenize 2013-01-22 23:25:36 -06:00
Winston Chang
8277b1192e Update NEWS 2013-01-22 23:23:02 -06:00
Winston Chang
150b978b0e Fix tests with reactiveValuesToList 2013-01-22 23:22:41 -06:00
Winston Chang
6c72096bfe Better CSS unit validation 2013-01-22 19:18:18 -06:00
Winston Chang
87c18cea80 Merge pull request #79 from wch/better-deps
Finer grained dependencies when converting reactiveValues to list
2013-01-22 17:15:38 -08:00
Winston Chang
e658734084 Rename reactivevalues_to_list to reactiveValuesToList 2013-01-22 19:14:30 -06:00
Winston Chang
ec4f350baa reactivevalues_to_list: add all.names option 2013-01-22 14:53:14 -06:00
Winston Chang
095f583211 Deprecate as.list.reactivevalues and add reactivevalues_to_list 2013-01-22 14:51:43 -06:00
Winston Chang
3c864cf6d2 reactiveValues(): improved check for unnamed arguments 2013-01-22 13:59:31 -06:00
Joe Cheng
eb4b21ce9f Fix #77: tagWriteChildren error 2013-01-21 22:40:08 -08:00
Joe Cheng
ff5349fd90 Fix #65: tagWrite doesn't expect strings except as direct children of tags 2013-01-21 16:31:09 -08:00
Winston Chang
1f34ffa85d plotOutput: check that height has proper format 2013-01-18 19:16:50 -06:00
Winston Chang
e98cab1f7c Fix test 2013-01-17 00:11:38 -06:00
Winston Chang
aabc9659a2 Update NEWS
Some news items were under the wrong version heading. Those have also been
fixed.
2013-01-16 23:00:07 -06:00
Winston Chang
8d8d308f7a Rename 'dependencies' to 'dependents' 2013-01-16 22:42:03 -06:00
Winston Chang
3ebd4595c6 Add read-write wrapper class for ReactiveValues 2013-01-16 19:02:26 -06:00
Winston Chang
7e1168946f Re-roxygenize 2013-01-16 16:08:12 -06:00
Joe Cheng
134689d8aa Remove subsetting operators from Map and Values
The correct operators would be [[/[[<- but since we don't use them I
just removed them instead.
2013-01-16 13:48:50 -08:00
Winston Chang
56282f9cbb Merge branch 'lazy' 2013-01-16 12:32:32 -06:00
Joe Cheng
b4713741b1 Two new recursion/circularity tests 2013-01-16 10:27:20 -08:00
Joe Cheng
e42fe3bd61 Fix problem with circular dependencies
The first of the included tests did not pass without the changes to
Observable. The problem occurred when a function read a reactive value
and then wrote it. Any dependents on the function would not receive
any invalidations, then or ever after.

The first problem was that the dirty state was unilaterally set to FALSE
after the function finished executing, which might not be accurate if
the function's newly created was invalidated during its own execution.
Instead we set dirty state to FALSE before executing. But to prevent
reentrant calls from thinking the cached value can be used, we add
a .running field that is also consulted during getValue.

The second problem was that Observable$getValue didn't register the
dependent until after updateValue. That is a problem if updateValue
creates *and* invalidates a context before returning. So now we
register the dependent before calling updateValue.
2013-01-15 17:37:26 -08:00
Winston Chang
4fd2dade60 reactiveTable: don't return blank if first element is NA. Fixes #71 2013-01-15 16:04:18 -06:00
Joe Cheng
e12b03504c Fix bad calls to on.exit
I didn't realize on.exit replaces previous calls to on.exit by default.
2013-01-15 12:07:27 -08:00
Winston Chang
153156c1fa Add back onInvalidate to Observer class 2013-01-15 11:13:46 -06:00
Winston Chang
3ecc69da2b Un-export execCount 2013-01-15 11:13:46 -06:00
Winston Chang
07ad29da41 Clarify isolation test 2013-01-15 11:13:46 -06:00
Winston Chang
7d0de0b26f Remove onInvalidateHint
The recent changes to onInvalidate make it do almost exactly the same thing.
2013-01-15 11:13:46 -06:00
Winston Chang
77fab9c78f Remove all pendingInvalidate 2013-01-15 11:13:46 -06:00
Winston Chang
3a8f3272c7 Don't call observers until flushReact() 2013-01-15 11:13:46 -06:00
Joe Cheng
2d44cbac1b Failing overreactivity test 2013-01-08 14:06:10 -06:00
Joe Cheng
893d72677b Try LIFO pendingInvalidates? 2013-01-08 14:06:10 -06:00
Joe Cheng
979eca4066 Add execCount 2013-01-08 14:06:10 -06:00
Joe Cheng
258d13e746 Add ctx$.label to help with debugging
Shows the code that the context "belongs" to.
2013-01-08 14:06:10 -06:00
Winston Chang
779531da5d Use lazy evaluation of reactive functions 2013-01-08 14:06:10 -06:00
Winston Chang
31d71006d7 Add tests for isolate() 2013-01-08 14:06:10 -06:00
Winston Chang
64ca66c062 Add test for reactive evaluation order 2013-01-08 14:06:10 -06:00
Winston Chang
6e1a2b3427 reactive tests: count number of times observers are run 2013-01-08 14:06:10 -06:00
Winston Chang
f585235192 Add reactivity tests 2013-01-08 14:06:10 -06:00
Winston Chang
9355643554 Update NEWS 2013-01-08 14:03:23 -06:00
Winston Chang
ccc6055926 Fix reactivity for empty checkbox groups. Fixes #58 2013-01-08 13:57:10 -06:00
Joe Cheng
6639446bb8 Update README.md 2013-01-07 22:39:07 -08:00
Joe Cheng
e2925c585f Add isolate function for accessing reactives non-reactively 2013-01-03 12:16:50 -08:00
Joe Cheng
6c76b0473c Add implementation of reactive values 2013-01-02 16:00:21 -08:00
Joe Cheng
e1e19632a5 Update URL in DESCRIPTION 2012-12-21 14:46:52 -08:00
Winston Chang
3e5364d5c0 Bump version number to .99 for development 2012-12-18 11:17:12 -06:00
30 changed files with 1657 additions and 268 deletions

View File

@@ -7,3 +7,4 @@
^shiny\.cmd$
^run\.R$
^\.gitignore$
^res$

View File

@@ -1,8 +1,8 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 0.2.4
Date: 2012-11-30
Version: 0.3.1
Date: 2013-01-23
Author: RStudio, Inc.
Maintainer: Winston Chang <winston@rstudio.com>
Description: Shiny makes it incredibly easy to build interactive web
@@ -25,8 +25,9 @@ Imports:
digest
Suggests:
markdown,
Cairo
URL: https://github.com/rstudio/shiny, http://rstudio.github.com/shiny/tutorial
Cairo,
testthat
URL: http://www.rstudio.com/shiny/
BugReports: https://github.com/rstudio/shiny/issues
Collate:
'map.R'
@@ -43,3 +44,4 @@ Collate:
'shinyui.R'
'slider.R'
'bootstrap.R'
'run-url.R'

View File

@@ -1,3 +1,23 @@
S3method("$",reactivevalues)
S3method("$<-",reactivevalues)
S3method("$<-",shinyoutput)
S3method("[",reactivevalues)
S3method("[<-",reactivevalues)
S3method("[[",reactivevalues)
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)
S3method(reactive,"function")
S3method(reactive,default)
export(HTML)
export(a)
export(addResourcePath)
export(animationOptions)
@@ -21,13 +41,13 @@ 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)
@@ -43,10 +63,14 @@ export(reactiveTable)
export(reactiveText)
export(reactiveTimer)
export(reactiveUI)
export(reactiveValues)
export(reactiveValuesToList)
export(repeatable)
export(runApp)
export(runExample)
export(runGist)
export(runGitHub)
export(runUrl)
export(selectInput)
export(shinyServer)
export(shinyUI)
@@ -56,8 +80,8 @@ export(sliderInput)
export(span)
export(strong)
export(submitButton)
export(tableOutput)
export(tabPanel)
export(tableOutput)
export(tabsetPanel)
export(tag)
export(tagAppendChild)
@@ -68,20 +92,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)

52
NEWS
View File

@@ -1,7 +1,50 @@
shiny 0.2.3
shiny 0.3.1
--------------------------------------------------------------------------------
* Ignore request variables for routing purposes
* 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.
@@ -9,6 +52,11 @@ shiny 0.2.3
* 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
--------------------------------------------------------------------------------

View File

@@ -742,7 +742,9 @@ verbatimTextOutput <- function(outputId) {
#'
#' Render a \link{reactivePlot} 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,7 +754,8 @@ 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)
}
@@ -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
}

View File

@@ -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,

View File

@@ -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,12 @@ Context <- setRefClass(
ReactiveEnvironment <- setRefClass(
'ReactiveEnvironment',
fields = c('.currentContext', '.nextId', '.pendingInvalidate'),
fields = c('.currentContext', '.nextId', '.pendingFlush'),
methods = list(
initialize = function() {
.currentContext <<- NULL
.nextId <<- 0L
.pendingInvalidate <<- list()
.pendingFlush <<- list()
},
nextId = function() {
.nextId <<- .nextId + 1L
@@ -90,17 +92,14 @@ 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
})
while (length(.pendingFlush) > 0) {
ctx <- .pendingFlush[[1]]
.pendingFlush <<- .pendingFlush[-1]
ctx$executeFlushCallbacks()
}
}
)

View File

@@ -1,61 +1,55 @@
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 <- 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 +59,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 +69,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,88 +100,218 @@ 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 Values class -----------------------
#' Create an object for storing reactive values
#'
#' This function returns an object for storing reactive values. It is similar
#' to a list, but with special capabilities for reactive programming. When you
#' read a value from it, the calling reactive function takes a reactive
#' dependency on that value, and when you write to it, it notifies any reactive
#' functions that depend on that value.
#'
#' @examples
#' # Create the object with no values
#' values <- reactiveValues()
#'
#' # Assign values to 'a' and 'b'
#' values$a <- 3
#' values[['b']] <- 4
#'
#' \dontrun{
#' # From within a reactive context, you can access values with:
#' values$a
#' values[['a']]
#' }
#'
#' # If not in a reactive context (e.g., at the console), you can use isolate()
#' # to retrieve the value:
#' isolate(values$a)
#' isolate(values[['a']])
#'
#' # Set values upon creation
#' values <- reactiveValues(a = 1, b = 2)
#' isolate(values$a)
#'
#' @param ... Objects that will be added to the reactivevalues object. All of
#' these objects must be named.
#'
#' @seealso \code{\link{isolate}}.
#'
#' @export
reactiveValues <- function(...) {
args <- list(...)
if ((length(args) > 0) && (is.null(names(args)) || any(names(args) == "")))
stop("All arguments passed to reactiveValues() must be named.")
values <- .createReactiveValues(ReactiveValues$new())
# Use .subset2() instead of [[, to avoid method dispatch
.subset2(values, 'impl')$mset(args)
values
}
`[<-.Values` <- function(values, name, value) {
values$set(name, value)
return(values)
}
.createValuesReader <- function(values) {
# Create a reactivevalues object
#
# @param values A ReactiveValues object
# @param readonly Should this object be read-only?
.createReactiveValues <- function(values = NULL, readonly = FALSE) {
acc <- list(impl=values)
class(acc) <- 'reactvaluesreader'
class(acc) <- 'reactivevalues'
attr(acc, 'readonly') <- readonly
return(acc)
}
#' @S3method $ reactvaluesreader
`$.reactvaluesreader` <- function(x, name) {
x[['impl']]$get(name)
#' @S3method $ reactivevalues
`$.reactivevalues` <- function(x, name) {
.subset2(x, 'impl')$get(name)
}
#' @S3method names reactvaluesreader
names.reactvaluesreader <- function(x) {
x[['impl']]$names()
#' @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 as.list reactvaluesreader
as.list.reactvaluesreader <- function(x, ...) {
x[['impl']]$toList()
#' @S3method [[<- reactivevalues
`[[<-.reactivevalues` <- `$<-.reactivevalues`
#' @S3method [ reactivevalues
`[.reactivevalues` <- function(values, name) {
stop("Single-bracket indexing of reactivevalues object is not allowed.")
}
#' @S3method [<- reactivevalues
`[<-.reactivevalues` <- function(values, name, value) {
stop("Single-bracket indexing of reactivevalues object is not allowed.")
}
#' @S3method names reactivevalues
names.reactivevalues <- function(x) {
.subset2(x, 'impl')$names()
}
#' @S3method names<- reactivevalues
`names<-.reactivevalues` <- function(x, value) {
stop("Can't assign names to reactivevalues object")
}
#' @S3method as.list reactivevalues
as.list.reactivevalues <- function(x, all.names=FALSE, ...) {
.Deprecated("reactiveValuesToList",
msg = paste("'as.list.reactivevalues' is deprecated. ",
"Use reactiveValuesToList instead.",
"\nPlease see ?reactiveValuesToList for more information.",
sep = ""))
reactiveValuesToList(x, all.names)
}
#' Convert a reactivevalues object to a list
#'
#' This function does something similar to what you might \code{\link{as.list}}
#' to do. The difference is that the calling context will take dependencies on
#' every object in the reactivevalues object. To avoid taking dependencies on
#' all the objects, you can wrap the call with \code{\link{isolate}()}.
#'
#' @param x A reactivevalues object.
#' @param all.names If \code{TRUE}, include objects with a leading dot. If
#' \code{FALSE} (the default) don't include those objects.
#' @examples
#' values <- reactiveValues(a = 1)
#' \dontrun{
#' reactiveValuesToList(values)
#' }
#'
#' # To get the objects without taking dependencies on them, use isolate().
#' # isolate() can also be used when calling from outside a reactive context (e.g.
#' # at the console)
#' isolate(reactiveValuesToList(values))
#'
#' @export
reactiveValuesToList <- function(x, all.names=FALSE) {
.subset2(x, 'impl')$toList(all.names)
}
Observable <- setRefClass(
'Observable',
fields = list(
.func = 'function',
.dependencies = 'Dependencies',
.initialized = 'logical',
.value = 'ANY'
.label = 'character',
.dependents = 'Dependents',
.dirty = '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 ",
"or more parameters; only functions without parameters can be ",
"reactive.")
.func <<- func
.initialized <<- FALSE
.dirty <<- TRUE
.running <<- FALSE
.label <<- label
.execCount <<- 0L
},
getValue = function() {
if (!.initialized) {
.initialized <<- TRUE
.dependents$register()
if (.dirty || .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()
.dirty <<- TRUE
.dependents$invalidate()
})
.execCount <<- .execCount + 1L
.dirty <<- FALSE
wasRunning <- .running
.running <<- TRUE
on.exit(.running <<- wasRunning)
ctx$run(function() {
.value <<- try(.func(), silent=FALSE)
result <- withVisible(try(.func(), silent=FALSE))
.visible <<- result$visible
.value <<- result$value
})
if (!identical(old.value, .value)) {
.dependencies$invalidate()
}
}
)
)
@@ -214,49 +343,67 @@ reactive <- function(x) {
}
#' @S3method reactive function
reactive.function <- function(x) {
return(Observable$new(x)$getValue)
return(Observable$new(x, deparse(substitute(x)))$getValue)
}
#' @S3method reactive default
reactive.default <- function(x) {
stop("Don't know how to make this object reactive!")
}
# Return the number of times that a reactive function or observer has been run
execCount <- function(x) {
if (is.function(x))
return(environment(x)$.execCount)
else if (is(x, 'Observer'))
return(x$.execCount)
else
stop('Unexpected argument to execCount')
}
Observer <- setRefClass(
'Observer',
fields = list(
.func = 'function',
.hintCallbacks = 'list'
.label = 'character',
.flushCallbacks = 'list',
.execCount = 'integer'
),
methods = list(
initialize = function(func) {
initialize = function(func, label) {
if (length(formals(func)) > 0)
stop("Can't make an observer from a function that takes parameters; ",
"only functions without parameters can be reactive.")
.func <<- func
.label <<- label
.execCount <<- 0L
# Defer the first running of this until flushReact is called
ctx <- Context$new()
ctx$onInvalidate(function() {
ctx <- Context$new(.label)
ctx$onFlush(function() {
run()
})
ctx$invalidate()
ctx$addPendingFlush()
},
run = function() {
ctx <- Context$new()
ctx <- Context$new(.label)
ctx$onInvalidate(function() {
run()
})
ctx$onInvalidateHint(function() {
lapply(.hintCallbacks, function(func) {
lapply(.flushCallbacks, function(func) {
func()
NULL
})
ctx$addPendingFlush()
})
ctx$onFlush(function() {
run()
})
.execCount <<- .execCount + 1L
ctx$run(.func)
},
onInvalidateHint = function(func) {
.hintCallbacks <<- c(.hintCallbacks, func)
onInvalidate = function(func) {
.flushCallbacks <<- c(.flushCallbacks, func)
}
)
)
@@ -282,8 +429,7 @@ Observer <- setRefClass(
#'
#' @export
observe <- function(func) {
Observer$new(func)
invisible()
invisible(Observer$new(func, deparse(substitute(func))))
}
#' Timer
@@ -307,11 +453,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 +465,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 +489,48 @@ invalidateLater <- function(millis) {
})
invisible()
}
#' Create a non-reactive scope for an expression
#'
#' Executes the given expression in a scope where reactive values or functions
#' can be read, but they cannot cause the reactive scope of the caller to be
#' re-evaluated when they change.
#'
#' Ordinarily, the simple act of reading a reactive value causes a relationship
#' to be established between the caller and the reactive value, where a change
#' to the reactive value will cause the caller to re-execute. (The same applies
#' for the act of getting a reactive function's value.) The \code{isolate}
#' function lets you read a reactive value or function without establishing this
#' relationship.
#'
#' @param expr An expression that can access reactive values or functions.
#'
#' @examples
#' \dontrun{
#' observer(function() {
#' input$saveButton # Do take a dependency on input$saveButton
#'
#' # isolate a simple expression
#' data <- get(isolate(input$dataset)) # No dependency on input$dataset
#' writeToDatabase(data)
#' })
#'
#' observer(function() {
#' input$saveButton # Do take a dependency on input$saveButton
#'
#' # isolate a whole block
#' data <- isolate({
#' a <- input$valueA # No dependency on input$valueA or input$valueB
#' b <- input$valueB
#' c(a=a, b=b)
#' })
#' writeToDatabase(data)
#' })
#' }
#' @export
isolate <- function(expr) {
ctx <- Context$new('[isolate]')
ctx$run(function() {
expr
})
}

157
R/run-url.R Normal file
View File

@@ -0,0 +1,157 @@
#' 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.
#'
#' @examples
#' \dontrun{
#' runGist(4034323)
#' runGist("https://gist.github.com/4034323")
#' }
#'
#' @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.
#'
#' @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)
}

View File

@@ -20,7 +20,7 @@ ShinyApp <- setRefClass(
.invalidatedOutputErrors = 'Map',
.progressKeys = 'character',
.fileUploadContext = 'FileUploadContext',
session = 'Values',
session = 'ReactiveValues',
token = 'character', # Used to identify this instance in URLs
plots = 'Map',
downloads = 'Map',
@@ -34,13 +34,13 @@ 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)
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
@@ -74,9 +74,9 @@ ShinyApp <- setRefClass(
}
else
.invalidatedOutputValues$set(name, value)
})
}, label)
obs$onInvalidateHint(function() {
obs$onInvalidate(function() {
showProgress(name)
})
}
@@ -229,7 +229,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
@@ -246,7 +246,7 @@ ShinyApp <- setRefClass(
tmpdata <- tempfile()
on.exit(unlink(tmpdata))
result <- try(Context$new()$run(function() { download$func(tmpdata) }))
result <- try(Context$new('[download]')$run(function() { download$func(tmpdata) }))
if (is(result, 'try-error')) {
return(httpResponse(500, 'text/plain',
attr(result, 'condition')$message))
@@ -295,10 +295,13 @@ ShinyApp <- setRefClass(
#' @S3method $<- shinyoutput
`$<-.shinyoutput` <- function(x, name, value) {
x[['impl']]$defineOutput(name, value)
x[['impl']]$defineOutput(name, value, deparse(substitute(value)))
return(invisible(x))
}
#' @S3method [[<- shinyoutput
`[[<-.shinyoutput` <- `$<-.shinyoutput`
resolve <- function(dir, relpath) {
abs.path <- file.path(dir, relpath)
if (!file.exists(abs.path))
@@ -730,8 +733,16 @@ startApp <- function(port=8101L) {
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
}
}
}
}
@@ -757,7 +768,7 @@ startApp <- function(port=8101L) {
shinyapp$session$mset(msg$data)
flushReact()
local({
serverFunc(input=.createValuesReader(shinyapp$session),
serverFunc(input=.createReactiveValues(shinyapp$session, readonly=TRUE),
output=.createOutputWriter(shinyapp))
})
},
@@ -767,7 +778,10 @@ startApp <- function(port=8101L) {
shinyapp$dispatch(msg)
)
flushReact()
shinyapp$flushOutput()
lapply(apps$values(), function(shinyapp) {
shinyapp$flushOutput()
NULL
})
}, ws_env)
message('\n', 'Listening on port ', port)
@@ -823,7 +837,7 @@ runApp <- function(appDir=getwd(),
orig.wd <- getwd()
setwd(appDir)
on.exit(setwd(orig.wd))
on.exit(setwd(orig.wd), add = TRUE)
require(shiny)
@@ -932,49 +946,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/%s/download', gist)
} else if(grepl('^https://gist.github.com/([0-9a-f]+)$', gist)) {
paste(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)
}

View File

@@ -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>',

View File

@@ -113,7 +113,7 @@ reactiveTable <- function(func, ...) {
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(
@@ -130,9 +130,10 @@ reactiveTable <- function(func, ...) {
#' 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
@@ -141,12 +142,26 @@ 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 func A function that may print output and/or return a printable R
#' object.
#'
#' @seealso \code{\link{reactiveText}} 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"))
return(paste(capture.output({
result <- withVisible(func())
if (result$visible)
print(result$value)
}), collapse="\n"))
})
}
@@ -166,10 +181,16 @@ reactivePrint <- function(func) {
#' @param func A function that returns an R object that can be used as an
#' argument to \code{cat}.
#'
#' @seealso \code{\link{reactivePrint}} 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"))
value <- func()
return(paste(capture.output(cat(value)), collapse="\n"))
})
}

View File

@@ -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()

View File

@@ -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")
```

View 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")
})

View File

@@ -0,0 +1,467 @@
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(function() {
values$A
})
funcB <- reactive(function() {
funcA()
values$A
})
obsC <- observe(function() {
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(function() {
values$A + 5
})
obsC <- observe(function() {
observed_value1 <<- funcB() * values$A
})
obsD <- observe(function() {
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(function() {
values$C <- values$A > 0
})
funcD <- reactive(function() {
values$C
})
obsE <- observe(function() {
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(function() {
values$A > 0
})
funcB <- reactive(function() {
funcA()
})
obsC <- observe(function() {
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(function() {
values$A + 5
})
obsC <- observe(function() {
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(function() {
values$A + 5
})
obsC <- observe(function() {
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(function() {
values$B + 100
})
# References to valueB and funcB are isolated
obsC <- observe(function() {
obsC_value <<-
values$A + isolate(values$B) + isolate(funcB())
})
# In contrast with obsC, this has a non-isolated reference to funcB
obsD <- observe(function() {
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("Circular refs/reentrancy in reactive functions work", {
values <- reactiveValues(A=3)
funcB <- reactive(function() {
# 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(function() {
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(function() {
if (values$A == 0)
return(0)
values$A <- values$A - 1
funcB()
})
obsC <- observe(function() {
funcB()
})
flushReact()
expect_equal(execCount(obsC), 2)
expect_equal(execCount(funcB), 6)
})
test_that("Non-reactive recursion", {
nonreactiveA <- 3
outputD <- NULL
funcB <- reactive(function() {
if (nonreactiveA == 0)
return(0)
nonreactiveA <<- nonreactiveA - 1
return(funcB())
})
obsC <- observe(function() {
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(function() {
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(function() {
values$A <- isolate(values$A) - 1
values$A
})
obsC <- observe(function() {
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(function() {
names(values)
})
# Dependent on all non-hidden objects
depValues <- observe(function() {
reactiveValuesToList(values)
})
# Dependent on all objects, including hidden
depAllValues <- observe(function() {
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)
values$A <- 2
flushReact()
expect_equal(execCount(depNames), 1)
expect_equal(execCount(depValues), 2)
expect_equal(execCount(depAllValues), 2)
values$.B <- 3
flushReact()
expect_equal(execCount(depNames), 1)
expect_equal(execCount(depValues), 2)
expect_equal(execCount(depAllValues), 3)
values$C <- 1
flushReact()
expect_equal(execCount(depNames), 2)
expect_equal(execCount(depValues), 3)
expect_equal(execCount(depAllValues), 4)
values$.D <- 1
flushReact()
expect_equal(execCount(depNames), 3)
expect_equal(execCount(depValues), 3)
expect_equal(execCount(depAllValues), 5)
})

23
inst/tests/test-tags.r Normal file
View 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
View File

@@ -0,0 +1,56 @@
context("text")
test_that("reactivePrint and reactiveText behavior is correct", {
expect_equal(isolate(reactivePrint(function() "foo")()),
'[1] "foo"')
expect_equal(isolate(reactivePrint(function() invisible("foo"))()),
'')
expect_equal(isolate(reactivePrint(function() { print("foo"); "bar"})()),
'[1] "foo"\n[1] "bar"')
expect_equal(isolate(reactivePrint(function() NULL)()),
'NULL')
expect_equal(isolate(reactivePrint(function() invisible())()),
'')
expect_equal(isolate(reactivePrint(function() 1:5)()),
'[1] 1 2 3 4 5')
expect_equal(isolate(reactiveText(function() "foo")()),
'foo')
expect_equal(isolate(reactiveText(function() 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(reactiveText(function() { print("foo"); "bar"})()))
expect_equal(ret, 'bar')
expect_equal(print_out, '[1] "foo"')
expect_equal(isolate(reactiveText(function() NULL)()),
'')
expect_equal(isolate(reactiveText(function() invisible())()),
'')
expect_equal(isolate(reactiveText(function() 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(function() 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(function() 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(function() 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(function() 10)
expect_identical(withVisible(isolate(f())), list(value=10, visible=TRUE))
expect_identical(withVisible(isolate(f())), list(value=10, visible=TRUE))
})

View File

@@ -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);
}

50
man/isolate.Rd Normal file
View File

@@ -0,0 +1,50 @@
\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 functions.}
}
\description{
Executes the given expression in a scope where reactive
values or functions can be read, but they cannot cause
the reactive scope of the caller to be re-evaluated when
they change.
}
\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 function's
value.) The \code{isolate} function lets you read a
reactive value or function without establishing this
relationship.
}
\examples{
\dontrun{
observer(function() {
input$saveButton # Do take a dependency on input$saveButton
# isolate a simple expression
data <- get(isolate(input$dataset)) # No dependency on input$dataset
writeToDatabase(data)
})
observer(function() {
input$saveButton # Do take a dependency on input$saveButton
# isolate a whole block
data <- isolate({
a <- input$valueA # No dependency on input$valueA or input$valueB
b <- input$valueB
c(a=a, b=b)
})
writeToDatabase(data)
})
}
}

View File

@@ -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}
}

View File

@@ -5,14 +5,15 @@
reactivePrint(func)
}
\arguments{
\item{func}{A function that returns a printable R
object.}
\item{func}{A function that may print output and/or
return a printable R object.}
}
\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.
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
@@ -22,5 +23,79 @@
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({
# reactivePrint captures any print output, converts it to a string, and
# returns it
visFun <- reactivePrint(function() "foo")
visFun()
# '[1] "foo"'
invisFun <- reactivePrint(function() invisible("foo"))
invisFun()
# ''
multiprintFun <- reactivePrint(function() {
print("foo");
"bar"
})
multiprintFun()
# '[1] "foo"\\n[1] "bar"'
nullFun <- reactivePrint(function() NULL)
nullFun()
# 'NULL'
invisNullFun <- reactivePrint(function() invisible(NULL))
invisNullFun()
# ''
vecFun <- reactivePrint(function() 1:5)
vecFun()
# '[1] 1 2 3 4 5'
# Contrast with reactiveText, which takes the value returned from the function
# and uses cat() to convert it to a string
visFun <- reactiveText(function() "foo")
visFun()
# 'foo'
invisFun <- reactiveText(function() invisible("foo"))
invisFun()
# 'foo'
multiprintFun <- reactiveText(function() {
print("foo");
"bar"
})
multiprintFun()
# 'bar'
nullFun <- reactiveText(function() NULL)
nullFun()
# ''
invisNullFun <- reactiveText(function() invisible(NULL))
invisNullFun()
# ''
vecFun <- reactiveText(function() 1:5)
vecFun()
# '1 2 3 4 5'
})
}
\seealso{
\code{\link{reactiveText}} for displaying the value
returned from a function, instead of the printed output.
}

View File

@@ -23,4 +23,73 @@
\code{cat}, inside a \code{\link[utils]{capture.output}}
call.
}
\examples{
isolate({
# reactivePrint captures any print output, converts it to a string, and
# returns it
visFun <- reactivePrint(function() "foo")
visFun()
# '[1] "foo"'
invisFun <- reactivePrint(function() invisible("foo"))
invisFun()
# ''
multiprintFun <- reactivePrint(function() {
print("foo");
"bar"
})
multiprintFun()
# '[1] "foo"\\n[1] "bar"'
nullFun <- reactivePrint(function() NULL)
nullFun()
# 'NULL'
invisNullFun <- reactivePrint(function() invisible(NULL))
invisNullFun()
# ''
vecFun <- reactivePrint(function() 1:5)
vecFun()
# '[1] 1 2 3 4 5'
# Contrast with reactiveText, which takes the value returned from the function
# and uses cat() to convert it to a string
visFun <- reactiveText(function() "foo")
visFun()
# 'foo'
invisFun <- reactiveText(function() invisible("foo"))
invisFun()
# 'foo'
multiprintFun <- reactiveText(function() {
print("foo");
"bar"
})
multiprintFun()
# 'bar'
nullFun <- reactiveText(function() NULL)
nullFun()
# ''
invisNullFun <- reactiveText(function() invisible(NULL))
invisNullFun()
# ''
vecFun <- reactiveText(function() 1:5)
vecFun()
# '1 2 3 4 5'
})
}
\seealso{
\code{\link{reactivePrint}} for capturing the print
output of a function, rather than the returned text
value.
}

47
man/reactiveValues.Rd Normal file
View 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 function takes a
reactive dependency on that value, and when you write to
it, it notifies any reactive functions that depend on
that value.
}
\examples{
# Create the object with no values
values <- reactiveValues()
# Assign values to 'a' and 'b'
values$a <- 3
values[['b']] <- 4
\dontrun{
# From within a reactive context, you can access values with:
values$a
values[['a']]
}
# If not in a reactive context (e.g., at the console), you can use isolate()
# to retrieve the value:
isolate(values$a)
isolate(values[['a']])
# Set values upon creation
values <- reactiveValues(a = 1, b = 2)
isolate(values$a)
}
\seealso{
\code{\link{isolate}}.
}

View 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))
}

View File

@@ -23,4 +23,10 @@
Download and launch a Shiny application that is hosted on
GitHub as a gist.
}
\examples{
\dontrun{
runGist(4034323)
runGist("https://gist.github.com/4034323")
}
}

41
man/runGitHub.Rd Normal file
View 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/")
}
}

41
man/runUrl.Rd Normal file
View File

@@ -0,0 +1,41 @@
\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.
}
\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/")
}
}

62
res/text-example.R Normal file
View File

@@ -0,0 +1,62 @@
isolate({
# reactivePrint captures any print output, converts it to a string, and
# returns it
visFun <- reactivePrint(function() "foo")
visFun()
# '[1] "foo"'
invisFun <- reactivePrint(function() invisible("foo"))
invisFun()
# ''
multiprintFun <- reactivePrint(function() {
print("foo");
"bar"
})
multiprintFun()
# '[1] "foo"\n[1] "bar"'
nullFun <- reactivePrint(function() NULL)
nullFun()
# 'NULL'
invisNullFun <- reactivePrint(function() invisible(NULL))
invisNullFun()
# ''
vecFun <- reactivePrint(function() 1:5)
vecFun()
# '[1] 1 2 3 4 5'
# Contrast with reactiveText, which takes the value returned from the function
# and uses cat() to convert it to a string
visFun <- reactiveText(function() "foo")
visFun()
# 'foo'
invisFun <- reactiveText(function() invisible("foo"))
invisFun()
# 'foo'
multiprintFun <- reactiveText(function() {
print("foo");
"bar"
})
multiprintFun()
# 'bar'
nullFun <- reactiveText(function() NULL)
nullFun()
# ''
invisNullFun <- reactiveText(function() invisible(NULL))
invisNullFun()
# ''
vecFun <- reactiveText(function() 1:5)
vecFun()
# '1 2 3 4 5'
})

4
tests/test-all.R Normal file
View File

@@ -0,0 +1,4 @@
library(testthat)
library(shiny)
test_package("shiny")