Compare commits

...

130 Commits

Author SHA1 Message Date
Winston Chang
42a80bad8e Undo license change 2013-03-28 10:45:26 -05:00
Joe Cheng
6e3e77f65d Code review feedback for version checking stuff 2013-03-25 11:26:16 -07:00
Winston Chang
e155f022a0 Bump version to 0.5.0 2013-03-22 22:49:55 -05:00
Joe Cheng
db65aab347 Give warning message on obsolete Shiny Server version 2013-03-22 14:33:44 -07:00
Winston Chang
a180c5f357 renderPlot: just return if width or height is NULL 2013-03-22 12:52:43 -05:00
Winston Chang
1c0279f17c Undo commit 'Remove redundant code in shiny.js'
The code wasn't actually redundant.
2013-03-22 12:51:29 -05:00
Winston Chang
8866eb292b Add section breaks in shiny.js 2013-03-21 21:09:16 -05:00
Winston Chang
6fdda3391e Send initial value of URL hash 2013-03-20 16:53:40 -05:00
Winston Chang
fdb8dd4e5b Remove redundant code in shiny.js 2013-03-20 13:22:21 -05:00
Joe Cheng
9a1d3783ee Unbind controls in htmlOutput before displaying error 2013-03-19 13:32:14 -07:00
Winston Chang
3841d9e322 Update license information 2013-03-18 15:33:12 -07:00
Winston Chang
e392eadf8a Update NEWS 2013-03-18 11:22:27 -07:00
Winston Chang
f743d5d0b5 New method for detecting hidden outputs 2013-03-17 11:08:28 -07:00
Winston Chang
4a76bf59ef Fix access to .clientData 2013-03-17 09:54:18 -07:00
Winston Chang
205b29e2f5 Update NEWS 2013-03-15 16:44:42 -05:00
Winston Chang
d511b82264 Add imageOutput function 2013-03-15 16:44:18 -05:00
Winston Chang
aaae112e60 Add to on.exit() instead of replace 2013-03-15 16:26:05 -05:00
Winston Chang
955fd6207f Change license to GPL>=2 2013-03-15 12:32:05 -05:00
Joe Cheng
4e56c96612 Fix allowDataUriScheme 2013-03-13 11:13:26 -07:00
Joe Cheng
dd046f3442 Merge remote-tracking branch 'jcheng5/master'
Conflicts:
	R/shiny.R
2013-03-13 10:47:06 -07:00
Winston Chang
5a947f83a1 Separate private and public fields for input and clientData 2013-03-12 21:41:38 -05:00
Winston Chang
b87b8b54fd Update NEWS 2013-03-12 16:14:28 -05:00
Winston Chang
233c0537a1 Merge pull request #122 from wch/url
Send URL components in clientData
2013-03-12 13:42:05 -07:00
Winston Chang
63d4798a50 Add tests for parseQueryString 2013-03-12 14:32:52 -05:00
Winston Chang
6c47517684 Move allowDataUriScheme into .clientdata 2013-03-12 14:24:48 -05:00
Winston Chang
c58b1a0143 Add parseQueryString function 2013-03-12 14:24:48 -05:00
Joe Cheng
f489d9131b File uploads failed when no content type was provided
The simple fix for this would've been to just guess the content
type on the server (or use empty string or something), but by
doing the fix this way we're also set up to handle uploads by
servers that don't allow custom headers on AJAX calls.
2013-03-12 09:50:01 -07:00
Winston Chang
f0109c5588 Send URL in clientdata 2013-03-11 18:44:58 -05:00
Winston Chang
c16becba56 renderTable: check for empty data frame. Fixes #55 2013-03-11 18:43:57 -05:00
Winston Chang
4605788696 Add informative comments 2013-03-11 18:43:03 -05:00
Joe Cheng
87908313cc Merge remote-tracking branch 'jcheng5/httpuv'
Conflicts:
	R/shiny.R
2013-03-11 09:36:47 -07:00
Winston Chang
9cc2eba7b8 Merge pull request #109 from wch/retina
Add clientdata channel for sending data

This branch includes support for Retina-resolution displays.
2013-03-11 07:45:24 -07:00
Winston Chang
2459cee57b Add renderImage function 2013-03-11 09:39:39 -05:00
Winston Chang
0bf6ce57ed renderPlot: send height and width along with image 2013-03-11 09:39:38 -05:00
Winston Chang
7041424f96 Add plotPNG function 2013-03-07 17:12:24 -06:00
Winston Chang
9509285c16 Rename ShinySession to saveFile 2013-03-07 17:12:24 -06:00
Winston Chang
e55ee0e65d Create ShinySession$sendFile() and use from renderPlot() 2013-03-07 17:12:18 -06:00
Winston Chang
9ea70497c2 Bump version to 0.4.1.99 for development 2013-03-06 17:26:35 -06:00
Joe Cheng
3389b9e9fd Memory-efficient file downloads 2013-03-05 23:06:13 -08:00
Winston Chang
76d4d54639 Allow shinyServer() to take clientData argument 2013-03-05 19:32:10 -06:00
Winston Chang
1b692b6c37 Rename shinyapp to shinysession, and .shinyout_xx to .clientdata_output_xx 2013-03-05 19:07:36 -06:00
Winston Chang
40d8cef1a2 Rename .metadata to clientData 2013-03-05 16:21:02 -06:00
Winston Chang
23550c0062 Add manageInputs() to handle metadata and normal inputs 2013-03-05 16:07:28 -06:00
Winston Chang
949bd940ee Partial change to reactiveValues 2013-03-04 20:33:29 -06:00
Winston Chang
79bdb9eed5 Add shiny metadata channel and send pixel ratio
This adds support for retina-resolution displays
2013-03-04 20:33:29 -06:00
Winston Chang
a141f08298 Bump version to 0.4.1 2013-03-04 20:27:48 -06:00
Joe Cheng
dee43a3911 Don't animate when showing file upload error 2013-03-04 14:47:26 -08:00
Joe Cheng
ef227d0139 More memory-efficient file uploading 2013-03-04 11:14:39 -08:00
Winston Chang
cbcf9ce645 reactivePlot: fix infinite recursion when height/width is function 2013-03-04 11:20:14 -06:00
Joe Cheng
0e5af2b16c Check for excessively large uploads before they begin
The onHeaders callback is supposed to be able to stop large uploads before
they begin, but do not appear to be having the desired effect. The browsers
continue uploading until completion, before noticing the response. To work
around this for now, upload the sizes explicitly when the job begins and
let Shiny pre-emptively reject the whole thing. This is also beneficial
in cases where multiple files are being uploaded and not all of them
exceed the maximum upload size.
2013-03-01 19:31:18 -08:00
Joe Cheng
85ca3a3b27 Update upload docs 2013-03-01 15:54:19 -08:00
Joe Cheng
fc5f5f3b6c Don't initiate file upload if no files were chosen 2013-02-28 21:14:06 -08:00
Joe Cheng
716fd8c0b9 File upload improvements
- Add "shiny.maxRequestSize" option
- Show upload progress
2013-02-28 21:06:06 -08:00
Joe Cheng
a517393c43 Remove dead upload code 2013-02-28 07:46:31 -08:00
Joe Cheng
c2311faffe httpuv-style file uploading
Use HTTP POST to upload files rather than sending 4K chunks
one at a time over the websocket. This is massively faster and
also means no binary websocket support is needed. In theory
this approach should be compatible with Shiny Server.

Currently the client side code still uses File API which means
IE8 and 9 users are out of luck.
2013-02-27 16:47:18 -08:00
Joe Cheng
fe453b0d66 Restore filter functionality 2013-02-26 16:59:56 -08:00
Joe Cheng
7e75b0fc02 eventloop package renamed to httpuv 2013-02-26 16:27:26 -08:00
Joe Cheng
11b0a0a73d Conform to API changes in eventloop package 2013-02-25 20:06:21 -08:00
Joe Cheng
82fdb5c3eb Greatly improve responsiveness of interruption on Windows 2013-02-25 15:14:24 -08:00
Joe Cheng
3f1d532c8b Restore startApp/serviceApp division of labor 2013-02-25 15:14:23 -08:00
Joe Cheng
f258b00aa7 Initial implementation on eventloop
Timers don't work yet
2013-02-25 15:13:30 -08:00
Winston Chang
4e71b9576d Update NEWS 2013-02-25 15:11:24 -06:00
Winston Chang
f36567a5cd reactivePlot: correctly pass width and height to renderPlot 2013-02-25 12:35:39 -06:00
Winston Chang
924ebb6c7f Bump version to 0.4.0.99 for development 2013-02-22 15:30:04 -06:00
Winston Chang
6e7e8eb44a Bump version to 0.4.0 2013-02-21 16:50:01 -06:00
Winston Chang
308c583254 setRatePolicy based on effectiveId. Fixes #110
Previously when getType() was defined for a type of object, shiny.js would
send updates immediately instead of applying the rate policy.
2013-02-20 11:39:22 -06:00
Winston Chang
97b2f7e5ca Fix call to manageHiddenOutputs in timer callback. Fixes #112 2013-02-19 12:20:49 -06:00
Winston Chang
3ea88a07d9 sliderInputBinding inherits from text instead of number. Fixes #110 2013-02-18 22:25:38 -06:00
Winston Chang
588f8bb96a Merge pull request #107 from wch/numeric-na
Empty numericInput gets converted to NA
2013-02-18 14:01:04 -08:00
Winston Chang
c93c0dd721 Update NEWS 2013-02-18 16:00:25 -06:00
Winston Chang
fc59c254fd Merge pull request #108 from wch/unused-hidden
Treat unused outputs as hidden
2013-02-18 13:56:49 -08:00
Winston Chang
2f8b6a150f Treat unused outputs as hidden 2013-02-18 15:53:31 -06:00
Winston Chang
db60ac5c17 Empty numericInput gets converted to NA 2013-02-18 15:11:41 -06:00
Winston Chang
e1f09853c5 Make shiny.deprecation.messages option actually work 2013-02-17 16:17:41 -06:00
Winston Chang
24656713a5 Remove unnecessary function() in renderXX 2013-02-17 12:02:00 -06:00
Winston Chang
7dd0269292 Update NEWS 2013-02-14 14:09:42 -06:00
Winston Chang
8b87cea7aa Merge pull request #104 from wch/reactive-exp
Change reactive() and observe() to take expressions
2013-02-14 12:08:18 -08:00
Winston Chang
c7559a6946 Suspend overwritten output objects 2013-02-14 12:14:08 -06:00
Winston Chang
945c6080ad Export exprToFunction 2013-02-14 11:48:01 -06:00
Winston Chang
44590965d1 Add renderXX Rd files 2013-02-14 11:48:01 -06:00
Winston Chang
7ab64d678f reactivePlot: call height and width properly 2013-02-14 11:48:01 -06:00
Winston Chang
e406a76b62 Update documentation for renderXX 2013-02-14 11:48:01 -06:00
Winston Chang
e26f175a8f Change reactiveXX to renderXX 2013-02-13 12:11:39 -06:00
Winston Chang
d4ab84745d Make function for expr-to-function conversion 2013-02-12 15:55:51 -06:00
Winston Chang
32dbc3101e Add shinyDeprecated function 2013-02-12 15:24:50 -06:00
Winston Chang
0a924eb718 Fix deprecation message for observe() 2013-02-12 15:24:50 -06:00
Winston Chang
a284327bfc Re-roxygenize 2013-02-12 15:24:50 -06:00
Winston Chang
2ea38d6ecc Clean up instances of reactive() and observe() 2013-02-12 15:24:50 -06:00
Winston Chang
6a34bbfddd Add label argument to reactive and observe 2013-02-12 15:24:50 -06:00
Winston Chang
58323ada4b Change references of reactive 'functions' to 'expressions' 2013-02-12 15:24:49 -06:00
Winston Chang
5fd723cb80 reactive() and observe() now take expressions 2013-02-12 15:24:49 -06:00
Winston Chang
5c626e6957 Documentation fixes 2013-02-12 15:24:39 -06:00
Winston Chang
5d949842eb Add garbage collection tests 2013-02-11 20:26:23 -06:00
Winston Chang
b595c17d78 observe: add option to start suspended 2013-02-11 19:48:22 -06:00
Winston Chang
b84973ba2b Remove leftover testing string 2013-02-11 19:36:06 -06:00
Winston Chang
61be49e7b2 Merge pull request #97 from wch/suspend-hidden
Suspend hidden outputs. Fixes #24
2013-02-11 16:48:39 -08:00
Winston Chang
8faf5659ee Re-roxygenize 2013-02-11 18:47:53 -06:00
Winston Chang
cc9267a646 manageHiddenOutputs: check that output object exists 2013-02-11 18:45:45 -06:00
Winston Chang
55838bb032 Call manageHiddenOutputs after timer callbacks 2013-02-11 18:37:18 -06:00
Winston Chang
67619ac5e8 Don't allow another flush if currently in one 2013-02-11 18:35:32 -06:00
Winston Chang
952b342859 Better checks for hidden output objects 2013-02-11 18:31:44 -06:00
Winston Chang
c7149c460d Add documentation for suspendWhenHidden option 2013-02-11 16:08:30 -06:00
Winston Chang
fd0613ea0e Call manageHiddenOutputs when suspendWhenHidden is set 2013-02-11 15:16:04 -06:00
Winston Chang
36d2dddc59 Run manageHiddenOutputs on app init 2013-02-09 00:02:52 -06:00
Joe Cheng
63c5b05584 Stop extra update message from occurring on startup 2013-02-08 16:37:55 -08:00
Winston Chang
4b235e5b87 Send output hidden state on init 2013-02-07 14:29:03 -06:00
Winston Chang
6c51fffdaa Fix tests 2013-02-07 14:29:03 -06:00
Winston Chang
5d6d638c85 Clarify suspend description 2013-02-07 14:29:03 -06:00
Winston Chang
90eb515167 Observer: .flushCallbacks to .invalidateCallbacks 2013-02-07 14:29:03 -06:00
Joe Cheng
17526711a2 Change resume behavior for Observer
Eliminate multiple runs when resumed multiple times
2013-02-07 14:29:03 -06:00
Winston Chang
cf0118e090 Add tests for suspended observers 2013-02-07 14:29:03 -06:00
Winston Chang
868d6fec42 Add suspended option to Observer 2013-02-07 14:29:03 -06:00
Winston Chang
851f5854bf Add outputOptions function 2013-02-07 14:29:03 -06:00
Winston Chang
eb5428c971 Suspend hidden outputs 2013-02-07 14:29:03 -06:00
Winston Chang
81188df7ef Update runUrl help and re-document 2013-02-07 10:46:20 -06:00
Winston Chang
9fd365cc41 isolate help: mention debugging use and fix typos 2013-02-06 14:38:12 -06:00
Winston Chang
999df6e40f httpResponse: make sure headers is a list. Fixes #102 2013-02-06 12:29:24 -06:00
Winston Chang
076d069568 runGist: accept new URL format with username 2013-02-06 12:06:14 -06:00
Joe Cheng
2738648197 Merge pull request #101 from jcheng5/chrome-frame
Chrome Frame compatibility
2013-02-05 15:18:03 -08:00
Joe Cheng
36013009a1 Chrome Frame compatibility 2013-02-05 15:15:03 -08:00
Winston Chang
1b60233862 Fix closing brace in isolate help 2013-02-05 10:56:54 -06:00
Winston Chang
2cba10dd05 Follow redirects with curl for http
The previous logic added the -L option to curl when downloading https, but
    not for http.
2013-02-04 13:06:15 -06:00
Winston Chang
b3944127ea Add note about using local() with isolate() 2013-02-01 15:16:33 -05:00
Winston Chang
f1674378ca Remove unneeded reactive() wrappers 2013-01-31 15:47:02 -05:00
Winston Chang
6f0191e1cf Block some operators for shinyoutput objects 2013-01-31 15:45:31 -05:00
Winston Chang
1848844be6 Cleaner method for creating objects with class 2013-01-30 15:06:17 -05:00
Winston Chang
8b6362c749 Add section markers 2013-01-30 15:04:55 -05:00
Winston Chang
d860d13361 Add comments to test 2013-01-30 15:04:50 -05:00
Winston Chang
4b077dbf4c Observers can be suspended/resumed 2013-01-30 14:47:19 -05:00
Winston Chang
40f73bbfe2 Bump version to 3.1.99 for development 2013-01-30 13:51:54 -05:00
64 changed files with 2860 additions and 934 deletions

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 0.3.1
Version: 0.5.0
Date: 2013-01-23
Author: RStudio, Inc.
Maintainer: Winston Chang <winston@rstudio.com>
@@ -18,7 +18,7 @@ Imports:
utils,
datasets,
methods,
websockets (>= 1.1.6),
httpuv (>= 1.0.5),
caTools,
RJSONIO,
xtable,
@@ -45,3 +45,4 @@ Collate:
'slider.R'
'bootstrap.R'
'run-url.R'
'imageutils.R'

View File

@@ -1,9 +1,13 @@
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)
@@ -15,8 +19,6 @@ 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)
@@ -32,6 +34,7 @@ export(downloadButton)
export(downloadHandler)
export(downloadLink)
export(em)
export(exprToFunction)
export(fileInput)
export(h1)
export(h2)
@@ -42,6 +45,7 @@ export(h6)
export(headerPanel)
export(helpText)
export(htmlOutput)
export(imageOutput)
export(img)
export(includeHTML)
export(includeMarkdown)
@@ -51,9 +55,12 @@ export(isolate)
export(mainPanel)
export(numericInput)
export(observe)
export(outputOptions)
export(p)
export(pageWithSidebar)
export(parseQueryString)
export(plotOutput)
export(plotPNG)
export(pre)
export(radioButtons)
export(reactive)
@@ -65,6 +72,12 @@ export(reactiveTimer)
export(reactiveUI)
export(reactiveValues)
export(reactiveValuesToList)
export(renderImage)
export(renderPlot)
export(renderPrint)
export(renderTable)
export(renderText)
export(renderUI)
export(repeatable)
export(runApp)
export(runExample)
@@ -95,5 +108,5 @@ export(wellPanel)
import(RJSONIO)
import(caTools)
import(digest)
import(websockets)
import(httpuv)
import(xtable)

50
NEWS
View File

@@ -1,3 +1,53 @@
shiny 0.5.0
--------------------------------------------------------------------------------
* Switch from websockets package for handling websocket connections to httpuv.
* New method for detecting hidden output objects. Instead of checking that
height and width are 0, it checks that the object or any ancestor in the DOM
has style display:none.
* Add `clientData` reactive values object, which carries information about the
client. This includes the hidden status of output objects, height/width plot
output objects, and the URL of the browser.
* Add `parseQueryString()` function.
* Add `renderImage()` function for sending arbitrary image files to the client,
and its counterpart, `imageOutput()`.
* Add support for high-resolution (Retina) displays.
* Fix bug #55, where `renderTable()` would throw error with an empty data frame.
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
--------------------------------------------------------------------------------

View File

@@ -293,8 +293,25 @@ numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA) {
#' File Upload Control
#'
#' Create a file upload control that can be used to upload one or more files.
#' \bold{Experimental feature. Only works in some browsers (primarily tested on
#' Chrome and Firefox).}
#' \bold{Does not work on older browsers, including Internet Explorer 9 and
#' earlier.}
#'
#' Whenever a file upload completes, the corresponding input variable is set
#' to a dataframe. This dataframe contains one row for each selected file, and
#' the following columns:
#' \describe{
#' \item{\code{name}}{The filename provided by the web browser. This is
#' \strong{not} the path to read to get at the actual data that was uploaded
#' (see
#' \code{datapath} column).}
#' \item{\code{size}}{The size of the uploaded data, in
#' bytes.}
#' \item{\code{type}}{The MIME type reported by the browser (for example,
#' \code{text/plain}), or empty string if the browser didn't know.}
#' \item{\code{datapath}}{The path to a temp file that contains the data that was
#' uploaded. This file may be deleted if the user performs another upload
#' operation.}
#' }
#'
#' @param inputId Input variable to assign the control's value to.
#' @param label Display label for the control.
@@ -313,7 +330,13 @@ fileInput <- function(inputId, label, multiple = FALSE, accept = NULL) {
tagList(
tags$label(label),
inputTag
inputTag,
tags$div(
id=paste(inputId, "_progress", sep=""),
class="progress progress-striped active shiny-file-input-progress",
tags$div(class="bar"),
tags$label()
)
)
}
@@ -708,7 +731,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 +746,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(
@@ -738,9 +761,30 @@ verbatimTextOutput <- function(outputId) {
pre(id = outputId, class = "shiny-text-output")
}
#' Create a plot output element
#' Create a image output element
#'
#' Render a \link{reactivePlot} within an application page.
#' Render a \link{renderImage} within an application page.
#' @param outputId output variable to read the image from
#' @param width Image 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 Image height
#' @return An image output element that can be included in a panel
#' @examples
#' # Show an image
#' mainPanel(
#' imageOutput("dataImage")
#' )
#' @export
imageOutput <- function(outputId, width = "100%", height="400px") {
style <- paste("width:", validateCssUnit(width), ";",
"height:", validateCssUnit(height))
div(id = outputId, class = "shiny-image-output", style = style)
}
#' Create an plot output element
#'
#' Render a \link{renderPlot} within an application page.
#' @param outputId output variable to read the plot from
#' @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
@@ -756,12 +800,12 @@ verbatimTextOutput <- function(outputId) {
plotOutput <- function(outputId, width = "100%", height="400px") {
style <- paste("width:", validateCssUnit(width), ";",
"height:", validateCssUnit(height))
div(id = outputId, class="shiny-plot-output", style = style)
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
@@ -779,7 +823,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

View File

@@ -28,16 +28,23 @@ FileUploadOperation <- setRefClass(
.files = 'data.frame',
.dir = 'character',
.currentFileInfo = 'list',
.currentFileData = 'ANY'
.currentFileData = 'ANY',
.pendingFileInfos = 'list'
),
methods = list(
initialize = function(parent, id, dir) {
initialize = function(parent, id, dir, fileInfos) {
.parent <<- parent
.id <<- id
.dir <<- dir
.pendingFileInfos <<- fileInfos
},
fileBegin = function(file) {
fileBegin = function() {
if (length(.pendingFileInfos) < 1)
stop("fileBegin called too many times")
file <- .pendingFileInfos[[1]]
.currentFileInfo <<- file
.pendingFileInfos <<- tail(.pendingFileInfos, -1)
filename <- file.path(.dir, as.character(length(.files)))
row <- data.frame(name=file$name, size=file$size, type=file$type,
@@ -57,6 +64,8 @@ FileUploadOperation <- setRefClass(
close(.currentFileData)
},
finish = function() {
if (length(.pendingFileInfos) > 0)
stop("File upload job was stopped prematurely")
.parent$onJobFinished(.id)
return(.files)
}
@@ -73,14 +82,14 @@ FileUploadContext <- setRefClass(
initialize = function(dir=tempdir()) {
.basedir <<- dir
},
createUploadOperation = function() {
createUploadOperation = function(fileInfos) {
while (TRUE) {
id <- paste(as.raw(runif(12, min=0, max=0xFF)), collapse='')
dir <- file.path(.basedir, id)
if (!dir.create(dir))
next
op <- FileUploadOperation$new(.self, id, dir)
op <- FileUploadOperation$new(.self, id, dir, fileInfos)
.operations$set(id, op)
return(id)
}

46
R/imageutils.R Normal file
View File

@@ -0,0 +1,46 @@
#' Run a plotting function and save the output as a PNG
#'
#' This function returns the name of the PNG file that it generates. In
#' essence, it calls \code{png()}, then \code{func()}, then \code{dev.off()}.
#' So \code{func} must be a function that will generate a plot when used this
#' way.
#'
#' 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 filename The name of the output file. Defaults to a temp file with
#' extension \code{.png}.
#' @param width Width in pixels.
#' @param height Height in pixels.
#' @param res Resolution in pixels per inch. This value is passed to
#' \code{\link{png}}. Note that this affects the resolution of PNG rendering in
#' R; it won't change the actual ppi of the browser.
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
#' These can be used to set the width, height, background color, etc.
#'
#' @export
plotPNG <- function(func, filename=tempfile(fileext='.png'),
width=400, height=400, res=72, ...) {
# 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(filename=filename, width=width, height=height, res=res, list(...)))
tryCatch(
func(),
finally=dev.off())
filename
}

View File

@@ -68,12 +68,18 @@ Context <- setRefClass(
ReactiveEnvironment <- setRefClass(
'ReactiveEnvironment',
fields = c('.currentContext', '.nextId', '.pendingFlush'),
fields = list(
.currentContext = 'ANY',
.nextId = 'integer',
.pendingFlush = 'list',
.inFlush = 'logical'
),
methods = list(
initialize = function() {
.currentContext <<- NULL
.nextId <<- 0L
.pendingFlush <<- list()
.inFlush <<- FALSE
},
nextId = function() {
.nextId <<- .nextId + 1L
@@ -96,6 +102,11 @@ ReactiveEnvironment <- setRefClass(
.pendingFlush <<- c(ctx, .pendingFlush)
},
flush = function() {
# 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]

View File

@@ -26,6 +26,8 @@ Dependents <- setRefClass(
)
# ReactiveValues ------------------------------------------------------------
ReactiveValues <- setRefClass(
'ReactiveValues',
fields = list(
@@ -112,13 +114,14 @@ ReactiveValues <- setRefClass(
)
# reactivevalues: S3 wrapper class for Values class -----------------------
# 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 function takes a reactive
#' 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.
#'
@@ -163,15 +166,15 @@ reactiveValues <- function(...) {
values
}
# Register the S3 class so that it can be used for a field in a Reference Class
setOldClass("reactivevalues")
# Create a reactivevalues object
#
# @param values A ReactiveValues object
# @param readonly Should this object be read-only?
.createReactiveValues <- function(values = NULL, readonly = FALSE) {
acc <- list(impl=values)
class(acc) <- 'reactivevalues'
attr(acc, 'readonly') <- readonly
return(acc)
structure(list(impl=values), class='reactivevalues', readonly=readonly)
}
#' @S3method $ reactivevalues
@@ -219,7 +222,7 @@ names.reactivevalues <- function(x) {
#' @S3method as.list reactivevalues
as.list.reactivevalues <- function(x, all.names=FALSE, ...) {
.Deprecated("reactiveValuesToList",
shinyDeprecated("reactiveValuesToList",
msg = paste("'as.list.reactivevalues' is deprecated. ",
"Use reactiveValuesToList instead.",
"\nPlease see ?reactiveValuesToList for more information.",
@@ -254,13 +257,15 @@ reactiveValuesToList <- function(x, all.names=FALSE) {
.subset2(x, 'impl')$toList(all.names)
}
# Observable ----------------------------------------------------------------
Observable <- setRefClass(
'Observable',
fields = list(
.func = 'function',
.label = 'character',
.dependents = 'Dependents',
.dirty = 'logical',
.invalidated = 'logical',
.running = 'logical',
.value = 'ANY',
.visible = 'logical',
@@ -269,11 +274,11 @@ Observable <- setRefClass(
methods = list(
initialize = function(func, label=deparse(substitute(func))) {
if (length(formals(func)) > 0)
stop("Can't make a reactive function from a function that takes one ",
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
.dirty <<- TRUE
.invalidated <<- TRUE
.running <<- FALSE
.label <<- label
.execCount <<- 0L
@@ -281,7 +286,7 @@ Observable <- setRefClass(
getValue = function() {
.dependents$register()
if (.dirty || .running) {
if (.invalidated || .running) {
.self$.updateValue()
}
@@ -296,12 +301,12 @@ Observable <- setRefClass(
.updateValue = function() {
ctx <- Context$new(.label)
ctx$onInvalidate(function() {
.dirty <<- TRUE
.invalidated <<- TRUE
.dependents$invalidate()
})
.execCount <<- .execCount + 1L
.dirty <<- FALSE
.invalidated <<- FALSE
wasRunning <- .running
.running <<- TRUE
@@ -316,41 +321,60 @@ Observable <- setRefClass(
)
)
#' 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")
}
#' @S3method reactive function
reactive.function <- function(x) {
return(Observable$new(x, deparse(substitute(x)))$getValue)
}
#' @S3method reactive default
reactive.default <- function(x) {
stop("Don't know how to make this object reactive!")
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
}
# Return the number of times that a reactive function or observer has been run
# 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)
@@ -360,16 +384,20 @@ execCount <- function(x) {
stop('Unexpected argument to execCount')
}
# Observer ------------------------------------------------------------------
Observer <- setRefClass(
'Observer',
fields = list(
.func = 'function',
.label = 'character',
.flushCallbacks = 'list',
.execCount = 'integer'
.invalidateCallbacks = 'list',
.execCount = 'integer',
.onResume = 'function',
.suspended = 'logical'
),
methods = list(
initialize = function(func, label) {
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.")
@@ -377,68 +405,133 @@ Observer <- setRefClass(
.func <<- func
.label <<- label
.execCount <<- 0L
.suspended <<- suspended
.onResume <<- function() NULL
# Defer the first running of this until flushReact is called
ctx <- Context$new(.label)
ctx$onFlush(function() {
run()
})
ctx$addPendingFlush()
.createContext()$invalidate()
},
run = function() {
.createContext = function() {
ctx <- Context$new(.label)
ctx$onInvalidate(function() {
lapply(.flushCallbacks, function(func) {
lapply(.invalidateCallbacks, function(func) {
func()
NULL
})
ctx$addPendingFlush()
})
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)
},
onInvalidate = function(func) {
.flushCallbacks <<- c(.flushCallbacks, 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) {
invisible(Observer$new(func, deparse(substitute(func))))
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.
@@ -492,22 +585,32 @@ invalidateLater <- function(millis) {
#' Create a non-reactive scope for an expression
#'
#' Executes the given expression in a scope where reactive values or functions
#' 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 function's value.) The \code{isolate}
#' function lets you read a reactive value or function without establishing this
#' 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.
#'
#' @param expr An expression that can access reactive values or functions.
#' 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{
#' observer(function() {
#' observe({
#' input$saveButton # Do take a dependency on input$saveButton
#'
#' # isolate a simple expression
@@ -515,7 +618,7 @@ invalidateLater <- function(millis) {
#' writeToDatabase(data)
#' })
#'
#' observer(function() {
#' observe({
#' input$saveButton # Do take a dependency on input$saveButton
#'
#' # isolate a whole block
@@ -526,7 +629,30 @@ invalidateLater <- function(millis) {
#' })
#' 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]')

View File

@@ -3,8 +3,9 @@
#' 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.
#' 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
@@ -13,8 +14,11 @@
#'
#' @examples
#' \dontrun{
#' runGist(4034323)
#' runGist("https://gist.github.com/4034323")
#' runGist(3239667)
#' runGist("https://gist.github.com/jcheng5/3239667")
#'
#' # Old URL format without username
#' runGist("https://gist.github.com/3239667")
#' }
#'
#' @export
@@ -25,7 +29,7 @@ runGist <- function(gist,
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)) {
} else if(grepl('^https://gist.github.com/([^/]+/)?([0-9a-f]+)$', gist)) {
paste(gist, '/download', sep='')
} else {
stop('Unrecognized gist identifier format')
@@ -87,6 +91,9 @@ runGitHub <- function(repo, username = getOption("github.user"),
#'
#' 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

661
R/shiny.R
View File

@@ -1,9 +1,9 @@
#' @docType package
#' @import websockets caTools RJSONIO xtable digest
#' @import httpuv caTools RJSONIO xtable digest
NULL
suppressPackageStartupMessages({
library(websockets)
library(httpuv)
library(RJSONIO)
})
@@ -12,53 +12,75 @@ createUniqueId <- function(bytes) {
paste(as.character(as.raw(floor(runif(bytes, min=1, max=255)))), collapse='')
}
ShinyApp <- setRefClass(
'ShinyApp',
ShinySession <- setRefClass(
'ShinySession',
fields = list(
.websocket = 'list',
.websocket = 'ANY',
.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 = 'ReactiveValues',
.input = 'ReactiveValues', # Internal object for normal input sent from client
.clientData = 'ReactiveValues', # Internal object for other data sent from the client
input = 'reactivevalues', # Externally-usable S3 wrapper object for .input
clientData = 'reactivevalues', # Externally-usable S3 wrapper object for .clientData
token = 'character', # Used to identify this instance in URLs
plots = 'Map',
files = 'Map', # For keeping track of files sent to client
downloads = 'Map',
allowDataUriScheme = 'logical'
closed = 'logical'
),
methods = list(
initialize = function(ws) {
.websocket <<- ws
initialize = function(websocket) {
.websocket <<- websocket
.invalidatedOutputValues <<- Map$new()
.invalidatedOutputErrors <<- Map$new()
.progressKeys <<- character(0)
closed <<- FALSE
# TODO: Put file upload context in user/app-specific dir if possible
.fileUploadContext <<- FileUploadContext$new()
session <<- ReactiveValues$new()
.input <<- ReactiveValues$new()
.clientData <<- ReactiveValues$new()
input <<- .createReactiveValues(.input, readonly=TRUE)
clientData <<- .createReactiveValues(.clientData, readonly=TRUE)
token <<- createUniqueId(16)
allowDataUriScheme <<- TRUE
.outputs <<- list()
.outputOptions <<- list()
},
close = function() {
closed <<- TRUE
for (output in .outputs) {
output$suspend()
}
},
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
\\code{shinysession} (in the future this list may expand, so it is a good idea
to also include \\code{...} in your function signature)."
# 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) {
orig <- func
func <- function() {
orig(name=name, shinyapp=.self)
orig(name=name, shinysession=.self)
}
}
obs <- Observer$new(function() {
obs <- observe({
value <- try(func(), silent=FALSE)
@@ -74,11 +96,15 @@ ShinyApp <- setRefClass(
}
else
.invalidatedOutputValues$set(name, value)
}, label)
}, label=label, suspended=TRUE)
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))
@@ -108,6 +134,12 @@ ShinyApp <- setRefClass(
by \\code{id} is in progress. There is currently no mechanism for
explicitly turning off progress for an output component; instead, all
progress is implicitly turned off when flushOutput is next called.'
# If app is already closed, be sure not to show progress, otherwise we
# will get an error because of the closed websocket
if (closed)
return()
if (id %in% .progressKeys)
return()
@@ -124,9 +156,10 @@ ShinyApp <- setRefClass(
.sendErrorResponse(msg, paste('Unknown method', msg$method))
}
value <- try(do.call(func, as.list(append(msg$args, msg$blobs))))
value <- try(do.call(func, as.list(append(msg$args, msg$blobs))),
silent=TRUE)
if (inherits(value, 'try-error')) {
.sendErrorResponse(msg, paste('Error:', as.character(value)))
.sendErrorResponse(msg, conditionMessage(attr(value, 'condition')))
}
else {
.sendResponse(msg, value)
@@ -151,39 +184,36 @@ ShinyApp <- setRefClass(
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)
.websocket$send(json)
},
# Public RPC methods
`@uploadInit` = function() {
return(list(jobId=.fileUploadContext$createUploadOperation()))
},
`@uploadFileBegin` = function(jobId, fileName, fileType, fileSize) {
.fileUploadContext$getUploadOperation(jobId)$fileBegin(list(
name=fileName, type=fileType, size=fileSize
))
invisible()
},
`@uploadFileChunk` = function(jobId, ...) {
args <- list(...)
if (length(args) != 1)
stop("Bad file chunk request")
.fileUploadContext$getUploadOperation(jobId)$fileChunk(args[[1]])
invisible()
},
`@uploadFileEnd` = function(jobId) {
.fileUploadContext$getUploadOperation(jobId)$fileEnd()
invisible()
`@uploadInit` = function(fileInfos) {
maxSize <- getOption('shiny.maxRequestSize', 5 * 1024 * 1024)
fileInfos <- lapply(fileInfos, function(fi) {
if (is.null(fi$type))
fi$type <- getContentType(tools::file_ext(fi$name))
fi
})
sizes <- sapply(fileInfos, function(fi){ fi$size })
if (maxSize > 0 && any(sizes > maxSize)) {
stop("Maximum upload size exceeded")
}
jobId <- .fileUploadContext$createUploadOperation(fileInfos)
return(list(jobId=jobId,
uploadUrl=paste('session', token, 'upload', jobId, sep='/')))
},
`@uploadEnd` = function(jobId, inputId) {
fileData <- .fileUploadContext$getUploadOperation(jobId)$finish()
session$set(inputId, fileData)
.input$set(inputId, fileData)
invisible()
},
# Provides a mechanism for handling direct HTTP requests that are posted
# to the session (rather than going through the websocket)
handleRequest = function(ws, header, subpath) {
handleRequest = function(req) {
# TODO: Turn off caching for the response
subpath <- req$PATH_INFO
matches <- regmatches(subpath,
regexec("^/([a-z]+)/([^?]*)",
@@ -192,12 +222,30 @@ ShinyApp <- setRefClass(
if (length(matches) == 0)
return(httpResponse(400, 'text/html', '<h1>Bad Request</h1>'))
if (matches[2] == 'plot') {
savedPlot <- plots$get(utils::URLdecode(matches[3]))
if (is.null(savedPlot))
if (matches[2] == 'file') {
savedFile <- files$get(utils::URLdecode(matches[3]))
if (is.null(savedFile))
return(httpResponse(404, 'text/html', '<h1>Not Found</h1>'))
return(httpResponse(200, savedPlot$contentType, savedPlot$data))
return(httpResponse(200, savedFile$contentType, savedFile$data))
}
if (matches[2] == 'upload' && identical(req$REQUEST_METHOD, "POST")) {
job <- .fileUploadContext$getUploadOperation(matches[3])
if (!is.null(job)) {
fileName <- req$HTTP_SHINY_FILE_NAME
fileType <- req$HTTP_SHINY_FILE_TYPE
fileSize <- req$CONTENT_LENGTH
job$fileBegin()
reqInput <- req$rook.input
while (length(buf <- reqInput$read(2^16)) > 0)
job$fileChunk(buf)
job$fileEnd()
return(httpResponse(200, 'text/plain', 'OK'))
}
}
if (matches[2] == 'download') {
@@ -245,16 +293,17 @@ ShinyApp <- setRefClass(
}
tmpdata <- tempfile()
on.exit(unlink(tmpdata))
result <- try(Context$new('[download]')$run(function() { download$func(tmpdata) }))
if (is(result, 'try-error')) {
unlink(tmpdata)
return(httpResponse(500, 'text/plain',
attr(result, 'condition')$message))
}
return(httpResponse(
200,
download$contentType %OR% getContentType(tools::file_ext(filename)),
readBin(tmpdata, 'raw', n=file.info(tmpdata)$size),
# owned=TRUE means tmpdata will be deleted after response completes
list(file=tmpdata, owned=TRUE),
c(
'Content-Disposition' = ifelse(
dlmatches[3] == '',
@@ -268,13 +317,33 @@ ShinyApp <- setRefClass(
return(httpResponse(404, 'text/html', '<h1>Not Found</h1>'))
},
savePlot = function(name, data, contentType) {
plots$set(name, list(data=data, contentType=contentType))
return(sprintf('session/%s/plot/%s?%s',
saveFileUrl = function(name, data, contentType, extra=list()) {
"Creates an entry in the file map for the data, and returns a URL pointing
to the file."
files$set(name, list(data=data, contentType=contentType))
return(sprintf('session/%s/file/%s?%s',
URLencode(token, TRUE),
URLencode(name, TRUE),
createUniqueId(8)))
},
# Send a file to the client
fileUrl = function(name, file, contentType='application/octet-stream') {
"Return a URL for a file to be sent to the client. If allowDataUriScheme
is TRUE, then the file will be base64 encoded and embedded in the URL.
Otherwise, a URL pointing to the file will be returned."
bytes <- file.info(file)$size
if (is.na(bytes))
return(NULL)
fileData <- readBin(file, 'raw', n=bytes)
if (isTRUE(.clientData$.values$allowDataUriScheme)) {
b64 <- base64encode(fileData)
return(paste('data:', contentType, ';base64,', b64, sep=''))
} else {
return(saveFileUrl(name, fileData, contentType))
}
},
registerDownload = function(name, filename, contentType, func) {
downloads$set(name, list(filename = filename,
@@ -283,25 +352,144 @@ 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 clientData variable, with the format
# "output_foo_hidden". (It comes from .clientdata_output_foo_hidden
# on the JS side)
# Some tricky stuff: instead of accessing names using input$names(),
# get the names directly via input$.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 output_foo_hidden flag,
# and hidden should be TRUE. In other words, NULL and TRUE should map to
# TRUE, FALSE should map to FALSE.
hidden <- .clientData$.values[[paste("output_", outputName, "_hidden",
sep="")]]
if (is.null(hidden)) hidden <- TRUE
if (hidden && .outputOptions[[outputName]][['suspendWhenHidden']]) {
.outputs[[outputName]]$suspend()
} else {
.outputs[[outputName]]$resume()
}
}
},
# Set the normal and client data input variables
manageInputs = function(data) {
data_names <- names(data)
# Separate normal input variables from client data input variables
clientdata_idx <- grepl("^.clientdata_", data_names)
# Set normal (non-clientData) input values
.input$mset(data[data_names[!clientdata_idx]])
# Strip off .clientdata_ from clientdata input names, and set values
input_clientdata <- data[data_names[clientdata_idx]]
names(input_clientdata) <- sub("^.clientdata_", "",
names(input_clientdata))
.clientData$mset(input_clientdata)
},
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)
.createOutputWriter <- function(shinysession) {
structure(list(impl=shinysession), class='shinyoutput')
}
#' @S3method $<- shinyoutput
`$<-.shinyoutput` <- function(x, name, value) {
x[['impl']]$defineOutput(name, value, deparse(substitute(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))
@@ -320,44 +508,37 @@ 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)
# TODO: Figure out what this means after httpuv migration
filter <- getOption('shiny.http.response.filter', NULL)
if (is.null(filter))
filter <- function(ws, header, response) response
filter <- function(req, response) response
function(ws, header) {
header <- fixupRequestPath(header)
response <- handler(ws, header)
function(req) {
response <- handler(req)
if (is.null(response))
response <- httpResponse(404, content="<h1>Not Found</h1>")
response <- filter(ws, header, response)
headers <- as.list(response$headers)
headers$'Content-Type' <- response$content_type
return(http_response(ws,
status=response$status,
content_type=response$content_type,
content=response$content,
headers=response$headers))
response <- filter(req, response)
return(list(status=response$status,
body=response$content,
headers=headers))
}
}
@@ -373,13 +554,13 @@ joinHandlers <- function(handlers) {
handlers <- handlers[!sapply(handlers, is.null)]
if (length(handlers) == 0)
return(function(ws, header) NULL)
return(function(req) NULL)
if (length(handlers) == 1)
return(handlers[[1]])
function(ws, header) {
function(req) {
for (handler in handlers) {
response <- handler(ws, header)
response <- handler(req)
if (!is.null(response))
return(response)
}
@@ -387,35 +568,39 @@ joinHandlers <- function(handlers) {
}
}
sessionHandler <- function(ws, header) {
path <- header$PATH
sessionHandler <- function(req) {
path <- req$PATH_INFO
if (is.null(path))
return(NULL)
matches <- regmatches(path, regexec('^/session/([0-9a-f]+)(/.*)$', path))
matches <- regmatches(path, regexec('^(/session/([0-9a-f]+))(/.*)$', path))
if (length(matches[[1]]) == 0)
return(NULL)
session <- matches[[1]][2]
subpath <- matches[[1]][3]
session <- matches[[1]][3]
subpath <- matches[[1]][4]
shinyapp <- appsByToken$get(session)
if (is.null(shinyapp))
shinysession <- appsByToken$get(session)
if (is.null(shinysession))
return(NULL)
return(shinyapp$handleRequest(ws, header, subpath))
subreq <- as.environment(as.list(req, all.names=TRUE))
subreq$PATH_INFO <- subpath
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, matches[[1]][2], sep='')
return(shinysession$handleRequest(subreq))
}
dynamicHandler <- function(filePath, dependencyFiles=filePath) {
lastKnownTimestamps <- NA
metaHandler <- function(ws, header) NULL
metaHandler <- function(req) NULL
if (!file.exists(filePath))
return(metaHandler)
cacheContext <- CacheContext$new()
return (function(ws, header) {
return (function(req) {
# Check if we need to rebuild
if (cacheContext$isDirty()) {
cacheContext$reset()
@@ -434,13 +619,16 @@ dynamicHandler <- function(filePath, dependencyFiles=filePath) {
clearClients()
}
return(metaHandler(ws, header))
return(metaHandler(req))
})
}
staticHandler <- function(root) {
return(function(ws, header) {
path <- header$PATH
return(function(req) {
if (!identical(req$REQUEST_METHOD, 'GET'))
return(NULL)
path <- req$PATH_INFO
if (is.null(path))
return(httpResponse(400, content="<h1>Bad Request</h1>"))
@@ -459,7 +647,6 @@ staticHandler <- function(root) {
})
}
apps <- Map$new()
appsByToken <- Map$new()
# Provide a character representation of the WS that can be used
@@ -470,11 +657,11 @@ wsToKey <- function(WS) {
.globals <- new.env()
.globals$clients <- function(ws, header) NULL
.globals$clients <- function(req) NULL
clearClients <- function() {
.globals$clients <- function(ws, header) NULL
.globals$clients <- function(req) NULL
}
@@ -537,8 +724,11 @@ addResourcePath <- function(prefix, directoryPath) {
func=staticHandler(directoryPath))
}
resourcePathHandler <- function(ws, header) {
path <- header$RESOURCE
resourcePathHandler <- function(req) {
if (!identical(req$REQUEST_METHOD, 'GET'))
return(NULL)
path <- req$PATH_INFO
match <- regexpr('^/([^/]+)/', path, perl=TRUE)
if (match == -1)
@@ -552,10 +742,11 @@ resourcePathHandler <- function(ws, header) {
suffix <- substr(path, 2 + len, nchar(path))
header$RESOURCE <- suffix
header <- fixupRequestPath(header)
subreq <- as.environment(as.list(req, all.names=TRUE))
subreq$PATH_INFO <- suffix
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, substr(path, 1, 2 + len), sep='')
return(resInfo$func(ws, header))
return(resInfo$func(subreq))
}
.globals$server <- NULL
@@ -584,7 +775,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)
#' })
#' })
@@ -680,113 +871,140 @@ startApp <- function(port=8101L) {
})
serverFunc <- .globals$server
ws_env <- create_server(
port=port,
webpage=httpServer(c(sessionHandler,
dynamicHandler(uiR),
wwwDir,
sys.www.root,
resourcePathHandler)))
set_callback('established', function(WS, ...) {
shinyapp <- ShinyApp$new(WS)
apps$set(wsToKey(WS), shinyapp)
appsByToken$set(shinyapp$token, shinyapp)
}, ws_env)
set_callback('closed', function(WS, ...) {
shinyapp <- apps$get(wsToKey(WS))
if (!is.null(shinyapp))
appsByToken$remove(shinyapp$token)
apps$remove(wsToKey(WS))
}, ws_env)
set_callback('receive', function(DATA, WS, ...) {
if (getOption('shiny.trace', FALSE)) {
if (as.raw(0) %in% DATA)
message("RECV ", '$$binary data$$')
else
message("RECV ", rawToChar(DATA))
}
if (identical(charToRaw("\003\xe9"), DATA))
return()
shinyapp <- apps$get(wsToKey(WS))
msg <- decodeMessage(DATA)
httpuvCallbacks <- list(
onHeaders = function(req) {
maxSize <- getOption('shiny.maxRequestSize', 5 * 1024 * 1024)
if (maxSize <= 0)
return(NULL)
# Do our own list simplifying here. sapply/simplify2array give names to
# character vectors, which is rarely what we want.
if (!is.null(msg$data)) {
for (name in names(msg$data)) {
val <- msg$data[[name]]
reqSize <- 0
if (length(req$CONTENT_LENGTH) > 0)
reqSize <- as.numeric(req$CONTENT_LENGTH)
else if (length(req$HTTP_TRANSFER_ENCODING) > 0)
reqSize <- Inf
if (reqSize > maxSize) {
return(list(status = 413L,
headers = list(
'Content-Type' = 'text/plain'
),
body = 'Maximum upload size exceeded'))
}
else {
return(NULL)
}
},
call = httpServer(c(sessionHandler,
dynamicHandler(uiR),
wwwDir,
sys.www.root,
resourcePathHandler)),
onWSOpen = function(ws) {
shinysession <- ShinySession$new(ws)
appsByToken$set(shinysession$token, shinysession)
ws$onMessage(function(binary, msg) {
splitName <- strsplit(name, ':')[[1]]
if (length(splitName) > 1) {
msg$data[[name]] <- NULL
# TODO: Make the below a user-extensible registry of deserializers
msg$data[[ splitName[[1]] ]] <- switch(
splitName[[2]],
matrix = unpackMatrix(val),
stop('Unknown type specified for ', name)
)
# To ease transition from websockets-based code. Should remove once we're stable.
if (is.character(msg))
msg <- charToRaw(msg)
if (getOption('shiny.trace', FALSE)) {
if (binary)
message("RECV ", '$$binary data$$')
else
message("RECV ", rawToChar(msg))
}
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
if (identical(charToRaw("\003\xe9"), msg))
return()
msg <- decodeMessage(msg)
# Do our own list simplifying here. sapply/simplify2array give names to
# character vectors, which is rarely what we want.
if (!is.null(msg$data)) {
for (name in names(msg$data)) {
val <- msg$data[[name]]
splitName <- strsplit(name, ':')[[1]]
if (length(splitName) > 1) {
msg$data[[name]] <- NULL
# TODO: Make the below a user-extensible registry of deserializers
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))) {
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
}
}
}
}
}
}
switch(
msg$method,
init = {
# Check if server.R has changed, and if so, reload
mtime <- file.info(serverR)$mtime
if (!identical(mtime, serverFileTimestamp)) {
shinyServer(NULL)
local({
serverFileTimestamp <<- mtime
source(serverR, local=new.env(parent=.GlobalEnv))
if (is.null(.globals$server))
stop("No server was defined in server.R")
})
serverFunc <<- .globals$server
}
shinyapp$allowDataUriScheme <- msg$data[['__allowDataUriScheme']]
msg$data[['__allowDataUriScheme']] <- NULL
shinyapp$session$mset(msg$data)
switch(
msg$method,
init = {
# Check if server.R has changed, and if so, reload
mtime <- file.info(serverR)$mtime
if (!identical(mtime, serverFileTimestamp)) {
shinyServer(NULL)
local({
serverFileTimestamp <<- mtime
source(serverR, local=new.env(parent=.GlobalEnv))
if (is.null(.globals$server))
stop("No server was defined in server.R")
})
serverFunc <<- .globals$server
}
shinysession$manageInputs(msg$data)
local({
args <- list(
input=shinysession$input,
output=.createOutputWriter(shinysession))
# The clientData argument is optional; check if it exists
if ('clientData' %in% names(formals(serverFunc)))
args$clientData <- shinysession$clientData
do.call(serverFunc, args)
})
},
update = {
shinysession$manageInputs(msg$data)
},
shinysession$dispatch(msg)
)
shinysession$manageHiddenOutputs()
flushReact()
local({
serverFunc(input=.createReactiveValues(shinyapp$session, readonly=TRUE),
output=.createOutputWriter(shinyapp))
lapply(appsByToken$values(), function(shinysession) {
shinysession$flushOutput()
NULL
})
},
update = {
shinyapp$session$mset(msg$data)
},
shinyapp$dispatch(msg)
)
flushReact()
lapply(apps$values(), function(shinyapp) {
shinyapp$flushOutput()
NULL
})
}, ws_env)
})
ws$onClose(function() {
shinysession$close()
appsByToken$remove(shinysession$token)
})
}
)
message('\n', 'Listening on port ', port)
return(ws_env)
return(startServer("0.0.0.0", port, httpuvCallbacks))
}
# NOTE: we de-roxygenized this comment because the function isn't exported
@@ -796,21 +1014,27 @@ startApp <- function(port=8101L) {
# @param ws_env The return value from \code{\link{startApp}}.
serviceApp <- function(ws_env) {
if (timerCallbacks$executeElapsed()) {
for (shinysession in appsByToken$values()) {
shinysession$manageHiddenOutputs()
}
flushReact()
lapply(apps$values(), function(shinyapp) {
shinyapp$flushOutput()
NULL
})
for (shinysession in appsByToken$values()) {
shinysession$flushOutput()
}
}
# If this R session is interactive, then call service() with a short timeout
# to keep the session responsive to user input
maxTimeout <- ifelse(interactive(), 100, 5000)
maxTimeout <- ifelse(interactive(), 100, 1000)
timeout <- max(1, min(maxTimeout, timerCallbacks$timeToNextEvent()))
service(server=ws_env, timeout=timeout)
service(timeout)
}
.shinyServerMinVersion <- '0.3.4'
#' Run Shiny Application
#'
#' Runs a Shiny application. This function normally does not return; interrupt
@@ -834,14 +1058,29 @@ runApp <- function(appDir=getwd(),
# Make warnings print immediately
ops <- options(warn = 1)
on.exit(options(ops))
if (nzchar(Sys.getenv('SHINY_PORT'))) {
# If SHINY_PORT is set, we're running under Shiny Server. Check the version
# to make sure it is compatible. Older versions of Shiny Server don't set
# SHINY_SERVER_VERSION, those will return "" which is considered less than
# any valid version.
ver <- Sys.getenv('SHINY_SERVER_VERSION')
if (compareVersion(ver, .shinyServerMinVersion) < 0) {
warning('Shiny Server v', .shinyServerMinVersion,
' or later is required; please upgrade!')
}
}
orig.wd <- getwd()
setwd(appDir)
on.exit(setwd(orig.wd), add = TRUE)
require(shiny)
ws_env <- startApp(port=port)
server <- startApp(port=port)
on.exit({
stopServer(server)
}, add = TRUE)
if (launch.browser) {
appUrl <- paste("http://localhost:", port, sep="")
@@ -850,11 +1089,11 @@ runApp <- function(appDir=getwd(),
tryCatch(
while (TRUE) {
serviceApp(ws_env)
serviceApp()
Sys.sleep(0.001)
},
finally = {
timerCallbacks$clear()
websocket_close(ws_env)
}
)
}
@@ -902,8 +1141,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") {

View File

@@ -188,9 +188,11 @@ shinyUI <- function(ui, path='/') {
registerClient({
function(ws, header) {
function(req) {
if (!identical(req$REQUEST_METHOD, 'GET'))
return(NULL)
if (header$PATH != path)
if (req$PATH_INFO != path)
return(NULL)
textConn <- textConnection(NULL, "w")

View File

@@ -5,19 +5,13 @@ 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
@@ -28,72 +22,187 @@ suppressPackageStartupMessages({
#' 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.
#' @param res Resolution of resulting plot, in pixels per inch. This value is
#' passed to \code{\link{png}}. Note that this affects the resolution of PNG
#' rendering in R; it won't change the actual ppi of the browser.
#' @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', res=72, ...,
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)
if (is.function(height))
height <- reactive(height)
widthWrapper <- reactive({ width() })
else
widthWrapper <- NULL
return(function(shinyapp, name, ...) {
png.file <- tempfile(fileext='.png')
if (is.function(width))
width <- width()
if (is.function(height))
height <- height()
if (is.function(height))
heightWrapper <- reactive({ height() })
else
heightWrapper <- NULL
return(function(shinysession, name, ...) {
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
# height were explicitly specified).
prefix <- '.shinyout_'
prefix <- 'output_'
if (width == 'auto')
width <- shinyapp$session$get(paste(prefix, name, '_width', sep=''));
width <- shinysession$clientData[[paste(prefix, name, '_width', sep='')]];
if (height == 'auto')
height <- shinyapp$session$get(paste(prefix, name, '_height', sep=''));
height <- shinysession$clientData[[paste(prefix, name, '_height', sep='')]];
if (width <= 0 || height <= 0)
if (is.null(width) || is.null(height) || width <= 0 || height <= 0)
return(NULL)
# 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
}
# Resolution multiplier
pixelratio <- shinysession$clientData$pixelratio
if (is.null(pixelratio))
pixelratio <- 1
do.call(pngfun, c(args, filename=png.file, width=width, height=height))
on.exit(unlink(png.file))
tryCatch(
func(),
finally=dev.off())
outfile <- do.call(plotPNG, c(func, width=width*pixelratio,
height=height*pixelratio, res=res*pixelratio, args))
on.exit(unlink(outfile))
bytes <- file.info(png.file)$size
if (is.na(bytes))
return(NULL)
pngData <- readBin(png.file, 'raw', n=bytes)
if (shinyapp$allowDataUriScheme) {
b64 <- base64encode(pngData)
return(paste("data:image/png;base64,", b64, sep=''))
}
else {
imageUrl <- shinyapp$savePlot(name, pngData, 'image/png')
return(imageUrl)
}
# Return a list of attributes for the img
return(list(
src=shinysession$fileUrl(name, outfile, contentType='image/png'),
width=width, height=height))
})
}
#' Image file output
#'
#' Renders a reactive image that is suitable for assigning to an \code{output}
#' slot.
#'
#' The expression \code{expr} must return a list containing the attributes for
#' the \code{img} object on the client web page. For the image to display,
#' properly, the list must have at least one entry, \code{src}, which is the
#' path to the image file. It may also useful to have a \code{contentType}
#' entry specifying the MIME type of the image. If one is not provided,
#' \code{renderImage} will try to autodetect the type, based on the file
#' extension.
#'
#' Other elements such as \code{width}, \code{height}, \code{class}, and
#' \code{alt}, can also be added to the list, and they will be used as
#' attributes in the \code{img} object.
#'
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
#' the CSS class name \code{shiny-image-output}.
#'
#' @param expr An expression that returns a list.
#' @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 deleteFile Should the file in \code{func()$src} be deleted after
#' it is sent to the client browser? Genrrally speaking, if the image is a
#' temp file generated within \code{func}, then this should be \code{TRUE};
#' if the image is not a temp file, this should be \code{FALSE}.
#'
#' @export
#'
#' @examples
#' \dontrun{
#'
#' shinyServer(function(input, output, clientData) {
#'
#' # A plot of fixed size
#' output$plot1 <- renderImage({
#' # A temp file to save the output. It will be deleted after renderImage
#' # sends it, because deleteFile=TRUE.
#' outfile <- tempfile(fileext='.png')
#'
#' # Generate a png
#' png(outfile, width=400, height=400)
#' hist(rnorm(input$n))
#' dev.off()
#'
#' # Return a list
#' list(src = outfile,
#' alt = "This is alternate text")
#' }, deleteFile = TRUE)
#'
#' # A dynamically-sized plot
#' output$plot2 <- renderImage({
#' # Read plot2's width and height. These are reactive values, so this
#' # expression will re-run whenever these values change.
#' width <- clientData$output_plot2_width
#' height <- clientData$output_plot2_height
#'
#' # A temp file to save the output.
#' outfile <- tempfile(fileext='.png')
#'
#' png(outfile, width=width, height=height)
#' hist(rnorm(input$obs))
#' dev.off()
#'
#' # Return a list containing the filename
#' list(src = outfile,
#' width = width,
#' height = height,
#' alt = "This is alternate text")
#' }, deleteFile = TRUE)
#'
#' # Send a pre-rendered image, and don't delete the image after sending it
#' output$plot3 <- renderImage({
#' # When input$n is 1, filename is ./images/image1.jpeg
#' filename <- normalizePath(file.path('./images',
#' paste('image', input$n, '.jpeg', sep='')))
#'
#' # Return a list containing the filename
#' list(src = filename)
#' }, deleteFile = FALSE)
#' })
#'
#' }
renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
deleteFile=TRUE) {
func <- exprToFunction(expr, env, quoted)
return(function(shinysession, name, ...) {
imageinfo <- func()
# Should the file be deleted after being sent? If .deleteFile not set or if
# TRUE, then delete; otherwise don't delete.
if (deleteFile) {
on.exit(unlink(imageinfo$src))
}
# If contentType not specified, autodetect based on extension
if (is.null(imageinfo$contentType)) {
contentType <- getContentType(sub('^.*\\.', '', basename(imageinfo$src)))
} else {
contentType <- imageinfo$contentType
}
# Extra values are everything in imageinfo except 'src' and 'contentType'
extra_attr <- imageinfo[!names(imageinfo) %in% c('src', 'contentType')]
# Return a list with src, and other img attributes
c(src = shinysession$fileUrl(name, file=imageinfo$src, contentType=contentType),
extra_attr)
})
}
#' Table Output
#'
#' Creates a reactive table that is suitable for assigning to an \code{output}
@@ -102,18 +211,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))
if (is.null(data) || identical(data, data.frame()))
return("")
return(paste(
@@ -125,7 +245,7 @@ reactiveTable <- function(func, ...) {
'"',
sep=''), ...)),
collapse="\n"))
})
}
}
#' Printable Output
@@ -146,23 +266,33 @@ reactiveTable <- function(func, ...) {
#' 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
#' @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{reactiveText}} for displaying the value returned from a
#' @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() {
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
@@ -178,20 +308,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{reactivePrint}} for capturing the print output of a
#' @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() {
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
@@ -202,28 +343,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)
# Wrap result in tagList in case it is an ordinary list
return(as.character(tagList(result)))
})
}
}
#' File Downloads
@@ -268,7 +420,64 @@ reactiveUI <- function(func) {
#'
#' @export
downloadHandler <- function(filename, content, contentType=NA) {
return(function(shinyapp, name, ...) {
shinyapp$registerDownload(name, filename, contentType, content)
return(function(shinysession, name, ...) {
shinysession$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() })
}

166
R/utils.R
View File

@@ -101,4 +101,168 @@ 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)
}
}
#' Parse a GET query string from a URL
#'
#' Returns a named character vector of key-value pairs.
#'
#' @param str The query string. It can have a leading \code{"?"} or not.
#' @export
#' @examples
#' parseQueryString("?foo=1&bar=b%20a%20r")
#'
#' \dontrun{
#' # Example of usage within a Shiny app
#' shinyServer(function(input, output, clientData) {
#'
#' output$queryText <- renderText({
#' query <- parseQueryString(clientData$url_search)
#'
#' # Ways of accessing the values
#' if (as.numeric(query$foo) == 1) {
#' # Do something
#' }
#' if (query[["bar"]] == "targetstring") {
#' # Do something else
#' }
#'
#' # Return a string with key-value pairs
#' paste(names(query), query, sep = "=", collapse=", ")
#' })
#' })
#' }
#'
parseQueryString <- function(str) {
if (is.null(str) || nchar(str) == 0)
return(list())
# Remove leading ?
if (substr(str, 1, 1) == '?')
str <- substr(str, 2, nchar(str))
pairs <- strsplit(str, '&', fixed = TRUE)[[1]]
pairs <- strsplit(pairs, '=', fixed = TRUE)
keys <- vapply(pairs, function(x) x[1], FUN.VALUE = character(1))
values <- vapply(pairs, function(x) x[2], FUN.VALUE = character(1))
# Replace NA with '', so they don't get converted to 'NA' by URLdecode
values[is.na(values)] <- ''
# Convert "+" to " ", since URLdecode doesn't do it
keys <- gsub('+', ' ', keys, fixed = TRUE)
values <- gsub('+', ' ', values, fixed = TRUE)
keys <- vapply(keys, function(x) URLdecode(x), FUN.VALUE = character(1))
values <- vapply(values, function(x) URLdecode(x), FUN.VALUE = character(1))
setNames(as.list(values), keys)
}
#' 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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,18 @@
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'
# columns. The 'data' column will contain the local filenames where the data
# can be found.
# file, it will be a data frame with 'name', 'size', 'type', and 'datapath'
# columns. The 'datapath' column will contain the local filenames where the
# data can be found.
inFile <- input$file1
if (is.null(inFile))
return(NULL)
read.csv(inFile$data, header=input$header, sep=input$sep, quote=input$quote)
read.csv(inFile$datapath, header=input$header, sep=input$sep, quote=input$quote)
})
})

View File

@@ -1,7 +1,7 @@
library(shiny)
shinyUI(pageWithSidebar(
headerPanel("CSV Viewer"),
headerPanel("Uploading Files"),
sidebarPanel(
fileInput('file1', 'Choose CSV File',
accept=c('text/csv', 'text/comma-separated-values,text/plain')),

View File

@@ -1,12 +1,12 @@
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()
})

View File

@@ -0,0 +1,6 @@
shinyServer(function(input, output) {
output$currentTime <- renderText({
invalidateLater(1000)
paste("The current time is", Sys.time())
})
})

View File

@@ -0,0 +1,3 @@
shinyUI(bootstrapPage(
textOutput("currentTime")
))

73
inst/tests/test-gc.r Normal file
View 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))
})

View File

@@ -59,16 +59,16 @@ test_that("Functions are not over-reactive", {
values <- reactiveValues(A=10)
funcA <- reactive(function() {
funcA <- reactive({
values$A
})
funcB <- reactive(function() {
funcB <- reactive({
funcA()
values$A
})
obsC <- observe(function() {
obsC <- observe({
funcB()
})
@@ -100,13 +100,13 @@ test_that("overreactivity2", {
observed_value2 <- NA
values <- reactiveValues(A=1)
funcB <- reactive(function() {
funcB <- reactive({
values$A + 5
})
obsC <- observe(function() {
obsC <- observe({
observed_value1 <<- funcB() * values$A
})
obsD <- observe(function() {
obsD <- observe({
observed_value2 <<- funcB() * values$A
})
@@ -135,15 +135,15 @@ test_that("overreactivity2", {
test_that("isolation", {
values <- reactiveValues(A=10, C=NULL)
obsB <- observe(function() {
obsB <- observe({
values$C <- values$A > 0
})
funcD <- reactive(function() {
funcD <- reactive({
values$C
})
obsE <- observe(function() {
obsE <- observe({
funcD()
})
@@ -163,15 +163,15 @@ test_that("laziness", {
values <- reactiveValues(A=10)
funcA <- reactive(function() {
funcA <- reactive({
values$A > 0
})
funcB <- reactive(function() {
funcB <- reactive({
funcA()
})
obsC <- observe(function() {
obsC <- observe({
if (values$A > 10)
return()
funcB()
@@ -203,10 +203,10 @@ test_that("order of evaluation", {
observed_value <- NA
values <- reactiveValues(A=1)
funcB <- reactive(function() {
funcB <- reactive({
values$A + 5
})
obsC <- observe(function() {
obsC <- observe({
observed_value <<- values$A * funcB()
})
@@ -230,10 +230,10 @@ test_that("order of evaluation", {
observed_value <- NA
values <- reactiveValues(A=1)
funcB <- reactive(function() {
funcB <- reactive({
values$A + 5
})
obsC <- observe(function() {
obsC <- observe({
observed_value <<- funcB() * values$A
})
@@ -259,18 +259,18 @@ test_that("isolate() blocks invalidations from propagating", {
obsD_value <- NA
values <- reactiveValues(A=1, B=10)
funcB <- reactive(function() {
funcB <- reactive({
values$B + 100
})
# References to valueB and funcB are isolated
obsC <- observe(function() {
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(function() {
obsD <- observe({
obsD_value <<-
values$A + isolate(values$B) + funcB()
})
@@ -309,11 +309,29 @@ test_that("isolate() blocks invalidations from propagating", {
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(function() {
funcB <- reactive({
# Each time fB executes, it reads and then writes valueA,
# effectively invalidating itself--until valueA becomes 0.
if (values$A == 0)
@@ -322,7 +340,7 @@ test_that("Circular refs/reentrancy in reactive functions work", {
return(values$A)
})
obsC <- observe(function() {
obsC <- observe({
funcB()
})
@@ -339,14 +357,14 @@ test_that("Circular refs/reentrancy in reactive functions work", {
test_that("Simple recursion", {
values <- reactiveValues(A=5)
funcB <- reactive(function() {
funcB <- reactive({
if (values$A == 0)
return(0)
values$A <- values$A - 1
funcB()
})
obsC <- observe(function() {
obsC <- observe({
funcB()
})
@@ -359,13 +377,13 @@ test_that("Non-reactive recursion", {
nonreactiveA <- 3
outputD <- NULL
funcB <- reactive(function() {
funcB <- reactive({
if (nonreactiveA == 0)
return(0)
nonreactiveA <<- nonreactiveA - 1
return(funcB())
})
obsC <- observe(function() {
obsC <- observe({
outputD <<- funcB()
})
@@ -377,7 +395,7 @@ test_that("Non-reactive recursion", {
test_that("Circular dep with observer only", {
values <- reactiveValues(A=3)
obsB <- observe(function() {
obsB <- observe({
if (values$A == 0)
return()
values$A <- values$A - 1
@@ -390,12 +408,12 @@ test_that("Circular dep with observer only", {
test_that("Writing then reading value is not circular", {
values <- reactiveValues(A=3)
funcB <- reactive(function() {
funcB <- reactive({
values$A <- isolate(values$A) - 1
values$A
})
obsC <- observe(function() {
obsC <- observe({
funcB()
})
@@ -413,17 +431,17 @@ test_that("names() and reactiveValuesToList()", {
values <- reactiveValues(A=1, .B=2)
# Dependent on names
depNames <- observe(function() {
depNames <- observe({
names(values)
})
# Dependent on all non-hidden objects
depValues <- observe(function() {
depValues <- observe({
reactiveValuesToList(values)
})
# Dependent on all objects, including hidden
depAllValues <- observe(function() {
depAllValues <- observe({
reactiveValuesToList(values, all.names = TRUE)
})
@@ -441,27 +459,194 @@ test_that("names() and reactiveValuesToList()", {
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)
})

View File

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

20
inst/tests/test-url.R Normal file
View File

@@ -0,0 +1,20 @@
context("URL")
test_that("Query string parsing", {
expect_identical(
parseQueryString("?foo=1&bar=b+a%20r&b+a%20z=baz&=nokey&novalue=&=&noequal&end=end"),
list(
foo = '1',
bar = 'b a r',
`b a z` = 'baz',
'nokey',
novalue = '',
'',
noequal = '',
end = 'end'
)
)
# Should be the same with or without leading question mark
expect_identical(parseQueryString("?foo=1&bar=b"), parseQueryString("foo=1&bar=b"))
})

View File

@@ -58,3 +58,27 @@ span.jslider {
.slider-animate-button.playing .play {
display: none;
}
.progress.shiny-file-input-progress {
position: relative;
visibility: hidden;
}
.progress.shiny-file-input-progress .bar {
overflow: hidden;
}
.progress.shiny-file-input-progress label {
display: block;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
font-size: 12px;
padding: 0 6px;
}
.progress.shiny-file-input-progress .bar.bar-danger {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
}

View File

@@ -57,6 +57,13 @@
}
}
function pixelRatio() {
if (window.devicePixelRatio) {
return window.devicePixelRatio;
} else {
return 1;
}
}
// Takes a string expression and returns a function that takes an argument.
//
@@ -70,6 +77,10 @@
}
// =========================================================================
// Input rate stuff
// =========================================================================
var Invoker = function(target, func) {
this.target = target;
this.func = func;
@@ -278,8 +289,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);
@@ -371,6 +387,9 @@
}
// =========================================================================
// ShinyApp
// =========================================================================
var ShinyApp = function() {
this.$socket = null;
@@ -400,7 +419,7 @@
$.extend(initialInput, {
// IE8 and IE9 have some limitations with data URIs
"__allowDataUriScheme": typeof WebSocket !== 'undefined'
".clientdata_allowDataUriScheme": typeof WebSocket !== 'undefined'
});
this.$socket = this.createSocket();
@@ -475,8 +494,7 @@
// response, the function will be called with it as the only argument.
// @param onError A function that will be called back if the server
// responds with error, or if the request fails for any other reason.
// The parameter to onError will be an error object or message (format
// TBD).
// The parameter to onError will be a string describing the error.
// @param blobs Optionally, an array of Blob, ArrayBuffer, or string
// objects that will be made available to the server as part of the
// request. Strings will be encoded using UTF-8.
@@ -584,7 +602,8 @@
}
if (msgObj.console) {
for (var i = 0; i < msgObj.console.length; i++) {
console.log(msgObj.console[i]);
if (console.log)
console.log(msgObj.console[i]);
}
}
if (msgObj.progress) {
@@ -668,24 +687,21 @@
}).call(ShinyApp.prototype);
// =========================================================================
// File Processor
// =========================================================================
// Generic driver class for doing chunk-wise asynchronous processing of a
// FileList object. Subclass/clone it and override the `on*` functions to
// make it do something useful.
var FileProcessor = function(files) {
this.files = files;
this.fileReader = new FileReader();
this.fileIndex = -1;
this.pos = 0;
// Currently need to use small chunk size because R-Websockets can't
// handle continuation frames
this.chunkSize = 4096;
this.aborted = false;
this.completed = false;
var self = this;
$(this.fileReader).on('load', function(evt) {
self.$endReadChunk();
});
// TODO: Register error/abort callbacks
this.$run();
@@ -695,13 +711,7 @@
this.onBegin = function(files, cont) {
setTimeout(cont, 0);
};
this.onFileBegin = function(file, cont) {
setTimeout(cont, 0);
};
this.onFileChunk = function(file, offset, blob, cont) {
setTimeout(cont, 0);
};
this.onFileEnd = function(file, cont) {
this.onFile = function(file, cont) {
setTimeout(cont, 0);
};
this.onComplete = function() {
@@ -758,49 +768,15 @@
// in the middle of processing a file, or have just finished
// processing a file.
var file = this.files[this.fileIndex];
if (this.pos >= file.size) {
// We've read past the end of this file--it's done
this.fileIndex++;
this.pos = 0;
this.onFileEnd(file, this.$getRun());
}
else if (this.pos == 0) {
// We're just starting with this file, need to call onFileBegin
// before we actually start reading
var called = false;
this.onFileBegin(file, function() {
if (called)
return;
called = true;
self.$beginReadChunk();
});
}
else {
// We're neither starting nor ending--just start the next chunk
this.$beginReadChunk();
}
};
// Starts asynchronous read of the current chunk of the current file
this.$beginReadChunk = function() {
var file = this.files[this.fileIndex];
var blob = slice(file, this.pos, this.pos + this.chunkSize);
this.fileReader.readAsArrayBuffer(blob);
};
// Called when a chunk has been successfully read
this.$endReadChunk = function() {
var file = this.files[this.fileIndex];
var offset = this.pos;
var data = this.fileReader.result;
this.pos = this.pos + this.chunkSize;
this.onFileChunk(file, offset, makeBlob([data]),
this.$getRun());
var file = this.files[this.fileIndex++];
this.onFile(file, this.$getRun());
};
}).call(FileProcessor.prototype);
// =========================================================================
// Binding registry
// =========================================================================
var BindingRegistry = function() {
this.bindings = [];
this.bindingNames = {};
@@ -839,6 +815,9 @@
var inputBindings = exports.inputBindings = new BindingRegistry();
var outputBindings = exports.outputBindings = new BindingRegistry();
// =========================================================================
// Output bindings
// =========================================================================
var OutputBinding = exports.OutputBinding = function() {};
(function() {
@@ -884,17 +863,20 @@
});
outputBindings.register(textOutputBinding, 'shiny.textOutput');
var plotOutputBinding = new OutputBinding();
$.extend(plotOutputBinding, {
var imageOutputBinding = new OutputBinding();
$.extend(imageOutputBinding, {
find: function(scope) {
return $(scope).find('.shiny-plot-output');
return $(scope).find('.shiny-image-output, .shiny-plot-output');
},
renderValue: function(el, data) {
// Load the image before emptying, to minimize flicker
var img = null;
if (data) {
img = document.createElement('img');
img.src = data;
// Copy items from data to img. This should include 'src'
$.each(data, function(key, value) {
img[key] = value;
})
}
$(el).empty();
@@ -902,13 +884,17 @@
$(el).append(img);
}
});
outputBindings.register(plotOutputBinding, 'shiny.plotOutput');
outputBindings.register(imageOutputBinding, 'shiny.imageOutput');
var htmlOutputBinding = new OutputBinding();
$.extend(htmlOutputBinding, {
find: function(scope) {
return $(scope).find('.shiny-html-output');
},
onValueError: function(el, err) {
exports.unbindAll(el);
this.renderError(el, err);
},
renderValue: function(el, data) {
exports.unbindAll(el);
$(el).html(data);
@@ -928,6 +914,9 @@
})
outputBindings.register(downloadLinkOutputBinding, 'shiny.downloadLink');
// =========================================================================
// Input bindings
// =========================================================================
var InputBinding = exports.InputBinding = function() {
};
@@ -1007,17 +996,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)
@@ -1133,59 +1127,115 @@
};
this.onBegin = function(files, cont) {
var self = this;
this.makeRequest(
'uploadInit', [],
function(response) {
self.jobId = response.jobId;
cont();
},
function(error) {
});
};
this.onFileBegin = function(file, cont) {
this.onProgress(file, 0);
this.makeRequest(
'uploadFileBegin', [this.jobId, file.name, file.type, file.size],
function(response) {
cont();
},
function(error) {
});
};
this.onFileChunk = function(file, offset, blob, cont) {
this.onProgress(file, (offset + blob.size) / file.size);
// Reset progress bar
this.$setError(null);
this.$setActive(true);
this.$setVisible(true);
this.onProgress(null, 0);
this.totalBytes = 0;
this.progressBytes = 0;
$.each(files, function(i, file) {
self.totalBytes += file.size;
});
var fileInfo = $.map(files, function(file, i) {
return {
name: file.name,
size: file.size,
type: file.type
};
});
this.makeRequest(
'uploadFileChunk', [this.jobId],
function(response) {
cont();
},
function(error) {
},
[blob]);
};
this.onFileEnd = function(file, cont) {
this.makeRequest(
'uploadFileEnd', [this.jobId],
'uploadInit', [fileInfo],
function(response) {
self.jobId = response.jobId;
self.uploadUrl = response.uploadUrl;
cont();
},
function(error) {
self.onError(error);
});
};
this.onFile = function(file, cont) {
var self = this;
this.onProgress(file, 0);
$.ajax(this.uploadUrl, {
type: 'POST',
cache: false,
xhr: function() {
var xhrVal = $.ajaxSettings.xhr();
if (xhrVal.upload) {
xhrVal.upload.onprogress = function(e) {
if (e.lengthComputable) {
self.onProgress(
file,
(self.progressBytes + e.loaded) / self.totalBytes);
}
}
}
return xhrVal;
},
data: file,
processData: false,
success: function() {
self.progressBytes += file.size;
cont();
},
error: function(jqXHR, textStatus, errorThrown) {
self.onError(jqXHR.responseText || textStatus);
}
});
};
this.onComplete = function() {
var self = this;
this.makeRequest(
'uploadEnd', [this.jobId, this.id],
function(response) {
self.$setActive(false);
self.onProgress(null, 1);
self.$label().text('Upload complete');
},
function(error) {
self.onError(error);
});
this.$label().text('Finishing upload');
};
this.onError = function(message) {
this.$setError(message || '');
this.$setActive(false);
};
this.onAbort = function() {
this.$setVisible(false);
};
this.onProgress = function(file, completed) {
console.log('file: ' + file.name + ' [' + Math.round(completed*100) + '%]');
this.$bar().width(Math.round(completed*100) + '%');
this.$label().text(file ? file.name : '');
};
this.$container = function() {
return $('#' + this.id + '_progress.shiny-file-input-progress');
};
this.$bar = function() {
return $('#' + this.id + '_progress.shiny-file-input-progress .bar');
};
this.$label = function() {
return $('#' + this.id + '_progress.shiny-file-input-progress label');
};
this.$setVisible = function(visible) {
this.$container().css('visibility', visible ? 'visible' : 'hidden');
};
this.$setError = function(error) {
this.$bar().toggleClass('bar-danger', (error !== null));
if (error !== null) {
this.onProgress(null, 1);
this.$label().text(error);
}
};
this.$setActive = function(active) {
this.$container().toggleClass('active', !!active);
};
}).call(FileUploader.prototype);
@@ -1200,6 +1250,9 @@
var files = evt.target.files;
var id = fileInputBinding.getId(evt.target);
if (files.length == 0)
return;
// Start the new upload and put the uploader in 'currentUploader'.
el.data('currentUploader', new FileUploader(exports.shinyapp, id, files));
};
@@ -1245,6 +1298,9 @@
}).call(OutputBindingAdapter.prototype);
// =========================================================================
// initShiny
// =========================================================================
function initShiny() {
var shinyapp = exports.shinyapp = new ShinyApp();
@@ -1277,7 +1333,8 @@
}
// Send later in case DOM layout isn't final yet.
setTimeout(sendPlotSize, 0);
setTimeout(sendImageSize, 0);
setTimeout(sendOutputHiddenState, 0);
}
function unbindOutputs(scope) {
@@ -1302,12 +1359,14 @@
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);
// By default, use rate decorator
inputs = inputsRate;
$('input[type="submit"], button[type="submit"]').each(function() {
// If there is a submit button on the page, use defer decorator
inputs = inputsDefer;
$(this).click(function(event) {
event.preventDefault();
@@ -1372,7 +1431,7 @@
var ratePolicy = binding.getRatePolicy();
if (ratePolicy != null) {
inputsRate.setRatePolicy(
id,
effectiveId,
ratePolicy.policy,
ratePolicy.delay);
}
@@ -1487,28 +1546,85 @@
var initialValues = _bindAll(document);
// 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;
// The server needs to know the size of each image and plot output element,
// in case it is auto-sizing
$('.shiny-image-output, .shiny-plot-output').each(function() {
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
initialValues['.clientdata_output_' + this.id + '_width'] = this.offsetWidth;
initialValues['.clientdata_output_' + 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);
function sendImageSize() {
$('.shiny-image-output, .shiny-plot-output').each(function() {
if (this.offsetWidth !== 0 || this.offsetHeight !== 0) {
inputs.setInput('.clientdata_output_' + this.id + '_width', this.offsetWidth);
inputs.setInput('.clientdata_output_' + this.id + '_height', this.offsetHeight);
}
});
}
// The size of each plot may change either because the browser window was
// Return true if the object or one of its ancestors in the DOM tree has
// style='display:none'; otherwise return false.
function isHidden(obj) {
// null means we've hit the top of the tree. If width or height is
// non-zero, then we know that no ancestor has display:none.
if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {
return false;
} else if (getComputedStyle(obj, null).display === 'none') {
return true;
} else {
return(isHidden(obj.parentNode));
}
}
// Set initial state of outputs to hidden, if needed
$('.shiny-bound-output').each(function() {
if (isHidden(this)) {
initialValues['.clientdata_output_' + this.id + '_hidden'] = true;
} else {
initialValues['.clientdata_output_' + 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 (isHidden(this)) {
inputs.setInput('.clientdata_output_' + this.id + '_hidden', true);
} else {
inputs.setInput('.clientdata_output_' + this.id + '_hidden', false);
}
});
}
// The size of each image may change either because the browser window was
// resized, or because a tab was shown/hidden (hidden elements report size
// 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);
$(window).resize(debounce(500, sendImageSize));
$('body').on('shown.sendImageSize', '*', sendImageSize);
$('body').on('shown.sendOutputHiddenState hidden.sendOutputHiddenState', '*',
sendOutputHiddenState);
// Send initial pixel ratio, and update it if it changes
initialValues['.clientdata_pixelratio'] = pixelRatio();
$(window).resize(function() {
inputs.setInput('.clientdata_pixelratio', pixelRatio());
});
// Send initial URL
initialValues['.clientdata_url_protocol'] = window.location.protocol;
initialValues['.clientdata_url_hostname'] = window.location.hostname;
initialValues['.clientdata_url_port'] = window.location.port;
initialValues['.clientdata_url_pathname'] = window.location.pathname;
initialValues['.clientdata_url_search'] = window.location.search;
// This is only the initial value of the hash. The hash can change, but
// a reactive version of this isn't sent because w atching for changes can
// require polling on some browsers. The JQuery hashchange plugin can be
// used if this capability is important.
initialValues['.clientdata_url_hash_initial'] = window.location.hash;
// We've collected all the initial values--start the server process!
inputsNoResend.reset(initialValues);
shinyapp.connect(initialValues);
} // function initShiny()

63
man/exprToFunction.Rd Normal file
View 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"
}

View File

@@ -20,8 +20,23 @@
}
\description{
Create a file upload control that can be used to upload
one or more files. \bold{Experimental feature. Only works
in some browsers (primarily tested on Chrome and
Firefox).}
one or more files. \bold{Does not work on older browsers,
including Internet Explorer 9 and earlier.}
}
\details{
Whenever a file upload completes, the corresponding input
variable is set to a dataframe. This dataframe contains
one row for each selected file, and the following
columns: \describe{ \item{\code{name}}{The filename
provided by the web browser. This is \strong{not} the
path to read to get at the actual data that was uploaded
(see \code{datapath} column).} \item{\code{size}}{The
size of the uploaded data, in bytes.}
\item{\code{type}}{The MIME type reported by the browser
(for example, \code{text/plain}), or empty string if the
browser didn't know.} \item{\code{datapath}}{The path to
a temp file that contains the data that was uploaded.
This file may be deleted if the user performs another
upload operation.} }
}

View File

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

29
man/imageOutput.Rd Normal file
View File

@@ -0,0 +1,29 @@
\name{imageOutput}
\alias{imageOutput}
\title{Create a image output element}
\usage{
imageOutput(outputId, width = "100\%", height = "400px")
}
\arguments{
\item{outputId}{output variable to read the image from}
\item{width}{Image 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}{Image height}
}
\value{
An image output element that can be included in a panel
}
\description{
Render a \link{renderImage} within an application page.
}
\examples{
# Show an image
mainPanel(
imageOutput("dataImage")
)
}

View File

@@ -6,11 +6,11 @@
}
\arguments{
\item{expr}{An expression that can access reactive values
or functions.}
or expressions.}
}
\description{
Executes the given expression in a scope where reactive
values or functions can be read, but they cannot cause
values or expression can be read, but they cannot cause
the reactive scope of the caller to be re-evaluated when
they change.
}
@@ -19,14 +19,26 @@
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.
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{
observer(function() {
observe({
input$saveButton # Do take a dependency on input$saveButton
# isolate a simple expression
@@ -34,7 +46,7 @@ observer(function() {
writeToDatabase(data)
})
observer(function() {
observe({
input$saveButton # Do take a dependency on input$saveButton
# isolate a whole block
@@ -45,6 +57,28 @@ observer(function() {
})
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
}

View File

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

38
man/parseQueryString.Rd Normal file
View File

@@ -0,0 +1,38 @@
\name{parseQueryString}
\alias{parseQueryString}
\title{Parse a GET query string from a URL}
\usage{
parseQueryString(str)
}
\arguments{
\item{str}{The query string. It can have a leading
\code{"?"} or not.}
}
\description{
Returns a named character vector of key-value pairs.
}
\examples{
parseQueryString("?foo=1&bar=b\%20a\%20r")
\dontrun{
# Example of usage within a Shiny app
shinyServer(function(input, output, clientData) {
output$queryText <- renderText({
query <- parseQueryString(clientData$url_search)
# Ways of accessing the values
if (as.numeric(query$foo) == 1) {
# Do something
}
if (query[["bar"]] == "targetstring") {
# Do something else
}
# Return a string with key-value pairs
paste(names(query), query, sep = "=", collapse=", ")
})
})
}
}

View File

@@ -1,6 +1,6 @@
\name{plotOutput}
\alias{plotOutput}
\title{Create a plot output element}
\title{Create an plot output element}
\usage{
plotOutput(outputId, width = "100\%", height = "400px")
}
@@ -18,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

43
man/plotPNG.Rd Normal file
View File

@@ -0,0 +1,43 @@
\name{plotPNG}
\alias{plotPNG}
\title{Run a plotting function and save the output as a PNG}
\usage{
plotPNG(func, filename = tempfile(fileext = ".png"),
width = 400, height = 400, res = 72, ...)
}
\arguments{
\item{func}{A function that generates a plot.}
\item{filename}{The name of the output file. Defaults to
a temp file with extension \code{.png}.}
\item{width}{Width in pixels.}
\item{height}{Height in pixels.}
\item{res}{Resolution in pixels per inch. This value is
passed to \code{\link{png}}. Note that this affects the
resolution of PNG rendering in R; it won't change the
actual ppi of the browser.}
\item{...}{Arguments to be passed through to
\code{\link[grDevices]{png}}. These can be used to set
the width, height, background color, etc.}
}
\description{
This function returns the name of the PNG file that it
generates. In essence, it calls \code{png()}, then
\code{func()}, then \code{dev.off()}. So \code{func} must
be a function that will generate a plot when used this
way.
}
\details{
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.
}

View File

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

View File

@@ -1,45 +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}.
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.
See \code{\link{renderPlot}}.
}

View File

@@ -1,101 +1,13 @@
\name{reactivePrint}
\alias{reactivePrint}
\title{Printable Output}
\title{Print output (deprecated)}
\usage{
reactivePrint(func)
}
\arguments{
\item{func}{A function that may print output and/or
return a printable R object.}
\item{func}{A function.}
}
\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({
# 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.
See \code{\link{renderPrint}}.
}

View File

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

View File

@@ -1,95 +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.
}
\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.
See \code{\link{renderText}}.
}

View File

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

View File

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

View File

@@ -13,7 +13,7 @@
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
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.

102
man/renderImage.Rd Normal file
View File

@@ -0,0 +1,102 @@
\name{renderImage}
\alias{renderImage}
\title{Image file output}
\usage{
renderImage(expr, env = parent.frame(), quoted = FALSE,
deleteFile = TRUE)
}
\arguments{
\item{expr}{An expression that returns a list.}
\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{deleteFile}{Should the file in \code{func()$src} be
deleted after it is sent to the client browser? Genrrally
speaking, if the image is a temp file generated within
\code{func}, then this should be \code{TRUE}; if the
image is not a temp file, this should be \code{FALSE}.}
}
\description{
Renders a reactive image that is suitable for assigning
to an \code{output} slot.
}
\details{
The expression \code{expr} must return a list containing
the attributes for the \code{img} object on the client
web page. For the image to display, properly, the list
must have at least one entry, \code{src}, which is the
path to the image file. It may also useful to have a
\code{contentType} entry specifying the MIME type of the
image. If one is not provided, \code{renderImage} will
try to autodetect the type, based on the file extension.
Other elements such as \code{width}, \code{height},
\code{class}, and \code{alt}, can also be added to the
list, and they will be used as attributes in the
\code{img} object.
The corresponding HTML output tag should be \code{div} or
\code{img} and have the CSS class name
\code{shiny-image-output}.
}
\examples{
\dontrun{
shinyServer(function(input, output, clientData) {
# A plot of fixed size
output$plot1 <- renderImage({
# A temp file to save the output. It will be deleted after renderImage
# sends it, because deleteFile=TRUE.
outfile <- tempfile(fileext='.png')
# Generate a png
png(outfile, width=400, height=400)
hist(rnorm(input$n))
dev.off()
# Return a list
list(src = outfile,
alt = "This is alternate text")
}, deleteFile = TRUE)
# A dynamically-sized plot
output$plot2 <- renderImage({
# Read plot2's width and height. These are reactive values, so this
# expression will re-run whenever these values change.
width <- clientData$output_plot2_width
height <- clientData$output_plot2_height
# A temp file to save the output.
outfile <- tempfile(fileext='.png')
png(outfile, width=width, height=height)
hist(rnorm(input$obs))
dev.off()
# Return a list containing the filename
list(src = outfile,
width = width,
height = height,
alt = "This is alternate text")
}, deleteFile = TRUE)
# Send a pre-rendered image, and don't delete the image after sending it
output$plot3 <- renderImage({
# When input$n is 1, filename is ./images/image1.jpeg
filename <- normalizePath(file.path('./images',
paste('image', input$n, '.jpeg', sep='')))
# Return a list containing the filename
list(src = filename)
}, deleteFile = FALSE)
})
}
}

54
man/renderPlot.Rd Normal file
View File

@@ -0,0 +1,54 @@
\name{renderPlot}
\alias{renderPlot}
\title{Plot Output}
\usage{
renderPlot(expr, width = "auto", height = "auto",
res = 72, ..., 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{res}{Resolution of resulting plot, in pixels per
inch. This value is passed to \code{\link{png}}. Note
that this affects the resolution of PNG rendering in R;
it won't change the actual ppi of the browser.}
\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}.
}

112
man/renderPrint.Rd Normal file
View 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
View 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
View 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
View 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
}

View File

@@ -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.}
@@ -25,8 +25,11 @@
}
\examples{
\dontrun{
runGist(4034323)
runGist("https://gist.github.com/4034323")
runGist(3239667)
runGist("https://gist.github.com/jcheng5/3239667")
# Old URL format without username
runGist("https://gist.github.com/3239667")
}
}

View File

@@ -27,7 +27,10 @@
\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.
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{

20
man/shinyDeprecated.Rd Normal file
View 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)}.
}

View File

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

View File

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

View File

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

View File

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

View File

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