mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 23:48:01 -05:00
Compare commits
135 Commits
v1.0.2
...
wch-test-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f66e9d5913 | ||
|
|
f1ff17ea94 | ||
|
|
397a6bfce4 | ||
|
|
cc25e95cb2 | ||
|
|
72adf0ab57 | ||
|
|
c5f04d276a | ||
|
|
29fc5bf720 | ||
|
|
d8887c20ca | ||
|
|
31f13b4846 | ||
|
|
6d46a942c9 | ||
|
|
43b571fbab | ||
|
|
30076b7975 | ||
|
|
855525e17b | ||
|
|
79480a63ae | ||
|
|
3782e515e8 | ||
|
|
4a2e1b65cb | ||
|
|
dc18b20e5a | ||
|
|
b4c5debbdf | ||
|
|
771d3d52b9 | ||
|
|
2a53ac093d | ||
|
|
4fa2af72cc | ||
|
|
e512d3cd61 | ||
|
|
16b7ee3985 | ||
|
|
4f3d26c31b | ||
|
|
587bf94d69 | ||
|
|
635ad77e0d | ||
|
|
33258da6c3 | ||
|
|
c2b3c3379d | ||
|
|
e30fac02ed | ||
|
|
e74592a654 | ||
|
|
ebd47aa73b | ||
|
|
e2d19cbaba | ||
|
|
1f864a846f | ||
|
|
fc32c2c944 | ||
|
|
279e37f1cb | ||
|
|
3f9176176e | ||
|
|
b3201ccafd | ||
|
|
2a01a620a9 | ||
|
|
6f43cf7b82 | ||
|
|
1c6250f9c2 | ||
|
|
650075a9ab | ||
|
|
668ee6f24a | ||
|
|
c456ec2c4c | ||
|
|
3b0c390a9e | ||
|
|
b02eb11345 | ||
|
|
ed3ba303bc | ||
|
|
ee5da1410e | ||
|
|
494627c6e1 | ||
|
|
82ac112dec | ||
|
|
40cfff33ff | ||
|
|
c1c5873912 | ||
|
|
c090efd562 | ||
|
|
91dbb0e77b | ||
|
|
dde7b144f0 | ||
|
|
f1873a014c | ||
|
|
48b8923b67 | ||
|
|
6f9f3fea83 | ||
|
|
10f3320165 | ||
|
|
d57aa33b40 | ||
|
|
0e7c78bae3 | ||
|
|
e6602786ec | ||
|
|
31bbb3894c | ||
|
|
8bbf576807 | ||
|
|
1ecc9b9d0e | ||
|
|
3adbebc3d9 | ||
|
|
a4c086f51b | ||
|
|
0ecdcec698 | ||
|
|
ae7f026d46 | ||
|
|
2813e0b706 | ||
|
|
a409562d00 | ||
|
|
b6b6661ea1 | ||
|
|
fb7b6f667c | ||
|
|
b94efe81e4 | ||
|
|
72a1b3d2a0 | ||
|
|
20bff18bd4 | ||
|
|
ba5c5ef4fb | ||
|
|
aff3ac0bb3 | ||
|
|
2c350daf01 | ||
|
|
cb7627c736 | ||
|
|
f731a5cae4 | ||
|
|
07cb7c9305 | ||
|
|
86e9cc4896 | ||
|
|
12c9405257 | ||
|
|
4708b44c59 | ||
|
|
4cb428bb92 | ||
|
|
d7391b19bc | ||
|
|
db9e56d1ca | ||
|
|
e527af10f4 | ||
|
|
74c7be0a6d | ||
|
|
2d40e7b51a | ||
|
|
ea407fb2ea | ||
|
|
fca5b0529a | ||
|
|
65fd1dd2d8 | ||
|
|
0a7ede3818 | ||
|
|
24e84f3866 | ||
|
|
c1c8e46c09 | ||
|
|
8591e4f301 | ||
|
|
10db7ad89c | ||
|
|
4ca4f442b9 | ||
|
|
6d5ecbc9c4 | ||
|
|
ea685a5686 | ||
|
|
376d3b6e91 | ||
|
|
df7397af1f | ||
|
|
9ba9345b04 | ||
|
|
9fc5758ae0 | ||
|
|
25298a6182 | ||
|
|
246da1bff6 | ||
|
|
8b5d12b958 | ||
|
|
3817370d4e | ||
|
|
c29846a9da | ||
|
|
2158f906a7 | ||
|
|
008dd280d6 | ||
|
|
fb99db011c | ||
|
|
c0fbd9cb3c | ||
|
|
fb79b18002 | ||
|
|
3841f22108 | ||
|
|
379d523ac5 | ||
|
|
07ec7f8c13 | ||
|
|
d0f29cc7a2 | ||
|
|
0e23a487f7 | ||
|
|
ac10f7c426 | ||
|
|
852c00009e | ||
|
|
b365798e66 | ||
|
|
66a6097a49 | ||
|
|
0e529d3d92 | ||
|
|
06c75dd656 | ||
|
|
69c32d4d90 | ||
|
|
36ffebd975 | ||
|
|
deb56539fb | ||
|
|
af8d099b9f | ||
|
|
eed869d321 | ||
|
|
f8f2acf6c3 | ||
|
|
7be9f74827 | ||
|
|
ed77982330 | ||
|
|
e1b47eca90 |
@@ -1,7 +1,7 @@
|
||||
^\.Rproj\.user$
|
||||
^\.git$
|
||||
^examples$
|
||||
^shiny\.Rproj$
|
||||
^.*\.Rproj$
|
||||
^shiny\.sh$
|
||||
^shiny\.cmd$
|
||||
^run\.R$
|
||||
@@ -18,3 +18,5 @@
|
||||
^.*\.o$
|
||||
^appveyor\.yml$
|
||||
^revdep$
|
||||
^tests/testthat/apps/\.git$
|
||||
^travis_phantomjs$
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "tests/testthat/apps"]
|
||||
path = tests/testthat/apps
|
||||
url = git@github.com:rstudio/shiny-test-apps.git
|
||||
31
.travis.yml
31
.travis.yml
@@ -1,10 +1,39 @@
|
||||
dist: trusty
|
||||
language: r
|
||||
r:
|
||||
- oldrel
|
||||
- release
|
||||
- devel
|
||||
sudo: false
|
||||
cache: packages
|
||||
|
||||
cache:
|
||||
packages: true
|
||||
# Use newer version of PhantomJS than is installed by default on basic R image.
|
||||
# Code referenced from:
|
||||
# https://github.com/travis-ci/travis-ci/issues/3225#issuecomment-255500144
|
||||
directories:
|
||||
- "travis_phantomjs"
|
||||
|
||||
# Need to replace the git: URL with https: so that Travis can check out the
|
||||
# submodule.
|
||||
git:
|
||||
submodules: false
|
||||
|
||||
before_install:
|
||||
# For updated version of PhantomJS
|
||||
- "export PHANTOMJS_VERSION=2.1.1"
|
||||
- "phantomjs --version"
|
||||
- "export PATH=$PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin:$PATH"
|
||||
- "hash -r"
|
||||
- "phantomjs --version"
|
||||
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi"
|
||||
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then wget https://github.com/Medium/phantomjs/releases/download/v$PHANTOMJS_VERSION/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2; fi"
|
||||
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi"
|
||||
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then hash -r; fi"
|
||||
- "phantomjs --version"
|
||||
# For git submodule
|
||||
- sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules
|
||||
- git submodule update --init --recursive
|
||||
|
||||
notifications:
|
||||
email:
|
||||
|
||||
@@ -2,7 +2,7 @@ We welcome contributions to the **shiny** package. To submit a contribution:
|
||||
|
||||
1. [Fork](https://github.com/rstudio/shiny/fork) the repository and make your changes.
|
||||
|
||||
2. If the change is non-trivial, ensure that you have signed the [individual](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioIndividualContributorAgreement.pdf) or [corporate](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioCorporateContributorAgreement.pdf) contributor agreement as appropriate. You can send the signed copy to jj@rstudio.com. For trivial changes (like typo fixes), a contributor agreement is not needed.
|
||||
2. Ensure that you have signed the [individual](https://rstudioblog.files.wordpress.com/2017/05/rstudio_individual_contributor_agreement.pdf) or [corporate](https://rstudioblog.files.wordpress.com/2017/05/rstudio_corporate_contributor_agreement.pdf) contributor agreement as appropriate. You can send the signed copy to jj@rstudio.com.
|
||||
|
||||
3. Submit a [pull request](https://help.github.com/articles/using-pull-requests).
|
||||
|
||||
|
||||
22
DESCRIPTION
22
DESCRIPTION
@@ -1,7 +1,7 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.0.2
|
||||
Version: 1.0.5.9000
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
|
||||
@@ -56,22 +56,23 @@ Authors@R: c(
|
||||
)
|
||||
Description: Makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive pre-built widgets make it possible to build
|
||||
outputs and extensive prebuilt widgets make it possible to build
|
||||
beautiful, responsive, and powerful applications with minimal effort.
|
||||
License: GPL-3 | file LICENSE
|
||||
Depends:
|
||||
R (>= 3.0.0),
|
||||
R (>= 3.0.2),
|
||||
methods
|
||||
Imports:
|
||||
utils,
|
||||
httpuv (>= 1.3.3),
|
||||
httpuv (>= 1.3.5),
|
||||
mime (>= 0.3),
|
||||
jsonlite (>= 0.9.16),
|
||||
xtable,
|
||||
digest,
|
||||
htmltools (>= 0.3.5),
|
||||
R6 (>= 2.0),
|
||||
sourcetools
|
||||
sourcetools,
|
||||
tools
|
||||
Suggests:
|
||||
datasets,
|
||||
Cairo (>= 1.5-5),
|
||||
@@ -80,10 +81,15 @@ Suggests:
|
||||
markdown,
|
||||
rmarkdown,
|
||||
ggplot2,
|
||||
magrittr
|
||||
magrittr,
|
||||
DT,
|
||||
shinytest
|
||||
Remotes:
|
||||
rstudio/shinytest,
|
||||
rstudio/DT
|
||||
URL: http://shiny.rstudio.com
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Collate:
|
||||
Collate:
|
||||
'app.R'
|
||||
'bookmark-state-local.R'
|
||||
'stack.R'
|
||||
@@ -122,6 +128,7 @@ Collate:
|
||||
'input-text.R'
|
||||
'input-textarea.R'
|
||||
'input-utils.R'
|
||||
'insert-tab.R'
|
||||
'insert-ui.R'
|
||||
'jqueryui.R'
|
||||
'middleware-shiny.R'
|
||||
@@ -143,6 +150,7 @@ Collate:
|
||||
'shinyui.R'
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'snapshot.R'
|
||||
'tar.R'
|
||||
'test-export.R'
|
||||
'timer.R'
|
||||
|
||||
11
NAMESPACE
11
NAMESPACE
@@ -40,6 +40,7 @@ export(actionButton)
|
||||
export(actionLink)
|
||||
export(addResourcePath)
|
||||
export(animationOptions)
|
||||
export(appendTab)
|
||||
export(as.shiny.appobj)
|
||||
export(basicPage)
|
||||
export(bookmarkButton)
|
||||
@@ -100,6 +101,7 @@ export(h5)
|
||||
export(h6)
|
||||
export(headerPanel)
|
||||
export(helpText)
|
||||
export(hideTab)
|
||||
export(hoverOpts)
|
||||
export(hr)
|
||||
export(htmlOutput)
|
||||
@@ -114,6 +116,7 @@ export(includeMarkdown)
|
||||
export(includeScript)
|
||||
export(includeText)
|
||||
export(inputPanel)
|
||||
export(insertTab)
|
||||
export(insertUI)
|
||||
export(installExprFunction)
|
||||
export(invalidateLater)
|
||||
@@ -121,6 +124,7 @@ export(is.reactive)
|
||||
export(is.reactivevalues)
|
||||
export(is.shiny.appobj)
|
||||
export(is.singleton)
|
||||
export(isRunning)
|
||||
export(isTruthy)
|
||||
export(isolate)
|
||||
export(knit_print.html)
|
||||
@@ -152,6 +156,7 @@ export(onReactiveDomainEnded)
|
||||
export(onRestore)
|
||||
export(onRestored)
|
||||
export(onSessionEnded)
|
||||
export(onStop)
|
||||
export(outputOptions)
|
||||
export(p)
|
||||
export(pageWithSidebar)
|
||||
@@ -161,6 +166,7 @@ export(passwordInput)
|
||||
export(plotOutput)
|
||||
export(plotPNG)
|
||||
export(pre)
|
||||
export(prependTab)
|
||||
export(printError)
|
||||
export(printStackTrace)
|
||||
export(radioButtons)
|
||||
@@ -180,6 +186,7 @@ export(registerInputHandler)
|
||||
export(removeInputHandler)
|
||||
export(removeModal)
|
||||
export(removeNotification)
|
||||
export(removeTab)
|
||||
export(removeUI)
|
||||
export(renderDataTable)
|
||||
export(renderImage)
|
||||
@@ -203,6 +210,7 @@ export(selectizeInput)
|
||||
export(serverInfo)
|
||||
export(setBookmarkExclude)
|
||||
export(setProgress)
|
||||
export(setSerializer)
|
||||
export(shinyApp)
|
||||
export(shinyAppDir)
|
||||
export(shinyAppFile)
|
||||
@@ -213,11 +221,14 @@ export(showBookmarkUrlModal)
|
||||
export(showModal)
|
||||
export(showNotification)
|
||||
export(showReactLog)
|
||||
export(showTab)
|
||||
export(sidebarLayout)
|
||||
export(sidebarPanel)
|
||||
export(singleton)
|
||||
export(sliderInput)
|
||||
export(snapshotExclude)
|
||||
export(snapshotPreprocessInput)
|
||||
export(snapshotPreprocessOutput)
|
||||
export(span)
|
||||
export(splitLayout)
|
||||
export(stopApp)
|
||||
|
||||
114
NEWS.md
114
NEWS.md
@@ -1,3 +1,117 @@
|
||||
shiny 1.0.5.9000
|
||||
================
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Breaking changes
|
||||
|
||||
### New features
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Changed script tags in reactlog ([inst/www/reactive-graph.html](https://github.com/rstudio/shiny/blob/master/inst/www/reactive-graph.html)) from HTTP to HTTPS in order to avoid mixed content blocking by most browsers. (Thanks, [@jekriske-lilly](https://github.com/jekriske-lilly)! [#1844](https://github.com/rstudio/shiny/pull/1844))
|
||||
|
||||
* The version of Shiny is now accessible from Javascript, with `Shiny.version`. There is also a new function for comparing version strings, `Shiny.compareVersion()`. ([#1826](https://github.com/rstudio/shiny/pull/1826), [#1830](https://github.com/rstudio/shiny/pull/1830))
|
||||
|
||||
* Addressed [#1784](https://github.com/rstudio/shiny/issues/1784): `runApp()` will avoid port 6697, which is considered unsafe by Chrome.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* The internal `URLdecode()` function previously was a copy of `httpuv::decodeURIComponent()`, assigned at build time; now it invokes the httpuv function at run time.
|
||||
|
||||
### Library updates
|
||||
|
||||
|
||||
shiny 1.0.5
|
||||
===========
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1818](https://github.com/rstudio/shiny/issues/1818): `conditionalPanel()` expressions that have a newline character in them caused the application to not work. ([#1820](https://github.com/rstudio/shiny/pull/1820))
|
||||
|
||||
* Added a safe wrapper function for internal calls to `jsonlite::fromJSON()`. ([#1822](https://github.com/rstudio/shiny/pull/1822))
|
||||
|
||||
* Fixed [#1824](https://github.com/rstudio/shiny/issues/1824): HTTP HEAD requests on static files caused the application to stop. ([#1825](https://github.com/rstudio/shiny/pull/1825))
|
||||
|
||||
|
||||
shiny 1.0.4
|
||||
===========
|
||||
|
||||
There are three headlining features in this release of Shiny. It is now possible to add and remove tabs from a `tabPanel`; there is a new function, `onStop()`, which registers callbacks that execute when an application exits; and `fileInput`s now can have files dragged and dropped on them. In addition to these features, this release has a number of minor features and bug fixes. See the full changelog below for more details.
|
||||
|
||||
## Full changelog
|
||||
|
||||
### New features
|
||||
|
||||
* Implemented [#1668](https://github.com/rstudio/shiny/issues/1668): dynamic tabs: added functions (`insertTab`, `appendTab`, `prependTab`, `removeTab`, `showTab` and `hideTab`) that allow you to do those actions for an existing `tabsetPanel`. ([#1794](https://github.com/rstudio/shiny/pull/1794))
|
||||
|
||||
* Implemented [#1213](https://github.com/rstudio/shiny/issues/1213): Added a new function, `onStop()`, which can be used to register callback functions that are invoked when an application exits, or when a user session ends. (Multiple sessions can be connected to a single running Shiny application.) This is useful if you have finalization/clean-up code that should be run after the application exits. ([#1770](https://github.com/rstudio/shiny/pull/1770)
|
||||
|
||||
* Implemented [#1155](https://github.com/rstudio/shiny/issues/1155): Files can now be drag-and-dropped on `fileInput` controls. The appearance of `fileInput` controls while files are being dragged can be modified by overriding the `shiny-file-input-active` and `shiny-file-input-over` classes. ([#1782](https://github.com/rstudio/shiny/pull/1782))
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Addressed [#1688](https://github.com/rstudio/shiny/issues/1688): trigger a new `shiny:outputinvalidated` event when an output gets invalidated, at the same time that the `recalculating` CSS class is added. ([#1758](https://github.com/rstudio/shiny/pull/1758), thanks [@andrewsali](https://github.com/andrewsali)!)
|
||||
|
||||
* Addressed [#1508](https://github.com/rstudio/shiny/issues/1508): `fileInput` now permits the same file to be uploaded multiple times. ([#1719](https://github.com/rstudio/shiny/pull/1719))
|
||||
|
||||
* Addressed [#1501](https://github.com/rstudio/shiny/issues/1501): The `fileInput` control now retains uploaded file extensions on the server. This fixes [readxl](https://github.com/tidyverse/readxl)'s `readxl::read_excel` and other functions that must recognize a file's extension in order to work. ([#1706](https://github.com/rstudio/shiny/pull/1706))
|
||||
|
||||
* For `conditionalPanel`s, Shiny now gives more informative messages if there are errors evaluating or parsing the JavaScript conditional expression. ([#1727](https://github.com/rstudio/shiny/pull/1727))
|
||||
|
||||
* Addressed [#1586](https://github.com/rstudio/shiny/issues/1586): The `conditionalPanel` function now accepts an `ns` argument. The `ns` argument can be used in a [module](https://shiny.rstudio.com/articles/modules.html) UI function to scope the `condition` expression to the module's own input and output IDs. ([#1735](https://github.com/rstudio/shiny/pull/1735))
|
||||
|
||||
* With `options(shiny.testmode=TRUE)`, the Shiny process will send a message to the client in response to a changed input, even if no outputs have changed. This helps to streamline testing using the shinytest package. ([#1747](https://github.com/rstudio/shiny/pull/1747))
|
||||
|
||||
* Addressed [#1738](https://github.com/rstudio/shiny/issues/1738): The `updateTextInput` and `updateTextAreaInput` functions can now update the placeholder. ([#1742](https://github.com/rstudio/shiny/pull/1742))
|
||||
|
||||
* Converted examples to single file apps, and made updates and enhancements to comments in the example app scripts. ([#1685](https://github.com/rstudio/shiny/pull/1685))
|
||||
|
||||
* Added new `snapshotPreprocessInput()` and `snapshotPreprocessOutput()` functions, which is used for preprocessing and input and output values before taking a test snapshot. ([#1760](https://github.com/rstudio/shiny/pull/1760), [#1789](https://github.com/rstudio/shiny/pull/1789))
|
||||
|
||||
* The HTML generated by `renderTable()` no longer includes comments with the R version, xtable version, and timestamp. ([#1771](https://github.com/rstudio/shiny/pull/1771))
|
||||
|
||||
* Added a function `isRunning` to test whether a Shiny app is currently running. ([#1785](https://github.com/rstudio/shiny/pull/1785))
|
||||
|
||||
* Added a function `setSerializer`, which allows authors to specify a function for serializing the value of a custom input. ([#1791](https://github.com/rstudio/shiny/pull/1791))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1546](https://github.com/rstudio/shiny/issues/1546): make it possible (without any hacks) to write arbitrary data into a module's `session$userData` (which is exactly the same environment as the parent's `session$userData`). To be clear, it allows something like `session$userData$x <- TRUE`, but not something like `session$userData <- TRUE` (that is not allowed in any context, whether you're in the main app, or in a module) ([#1732](https://github.com/rstudio/shiny/pull/1732)).
|
||||
|
||||
* Fixed [#1701](https://github.com/rstudio/shiny/issues/1701): There was a partial argument match in the `generateOptions` function. ([#1702](https://github.com/rstudio/shiny/pull/1702))
|
||||
|
||||
* Fixed [#1710](https://github.com/rstudio/shiny/issues/1710): `ReactiveVal` objects did not have separate dependents. ([#1712](https://github.com/rstudio/shiny/pull/1712))
|
||||
|
||||
* Fixed [#1438](https://github.com/rstudio/shiny/issues/1438): `unbindAll()` should not be called when inserting content with `insertUI()`. A previous fix ([#1449](https://github.com/rstudio/shiny/pull/1449)) did not work correctly. ([#1736](https://github.com/rstudio/shiny/pull/1736))
|
||||
|
||||
* Fixed [#1755](https://github.com/rstudio/shiny/issues/1755): dynamic htmlwidgets sent the path of the package on the server to the client. ([#1756](https://github.com/rstudio/shiny/pull/1756))
|
||||
|
||||
* Fixed [#1763](https://github.com/rstudio/shiny/issues/1763): Shiny's private random stream leaked out into the main random stream. ([#1768](https://github.com/rstudio/shiny/pull/1768))
|
||||
|
||||
* Fixed [#1680](https://github.com/rstudio/shiny/issues/1680): `options(warn=2)` was not respected when running an app. ([#1790](https://github.com/rstudio/shiny/pull/1790))
|
||||
|
||||
* Fixed [#1772](https://github.com/rstudio/shiny/issues/1772): ensure that `runApp()` respects the `shinyApp(onStart = function())` argument. ([#1770](https://github.com/rstudio/shiny/pull/1770))
|
||||
|
||||
* Fixed [#1474](https://github.com/rstudio/shiny/issues/1474): A `browser()` call in an observer could cause an error in the RStudio IDE on Windows. ([#1802](https://github.com/rstudio/shiny/pull/1802))
|
||||
|
||||
|
||||
shiny 1.0.3
|
||||
================
|
||||
|
||||
This is a hotfix release of Shiny. With previous versions of Shiny, when running an application on the newly-released version of R, 3.4.0, it would print a message: `Warning in body(fun) : argument is not a function`. This has no effect on the application, but because the message could be alarming to users, we are releasing a new version of Shiny that fixes this issue.
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1672](https://github.com/rstudio/shiny/issues/1672): When an error occurred while uploading a file, the progress bar did not change colors. ([#1673](https://github.com/rstudio/shiny/pull/1673))
|
||||
|
||||
* Fixed [#1676](https://github.com/rstudio/shiny/issues/1676): On R 3.4.0, running a Shiny application gave a warning: `Warning in body(fun) : argument is not a function`. ([#1677](https://github.com/rstudio/shiny/pull/1677))
|
||||
|
||||
|
||||
shiny 1.0.2
|
||||
================
|
||||
|
||||
|
||||
11
R/app.R
11
R/app.R
@@ -71,7 +71,7 @@
|
||||
#' }
|
||||
#' @export
|
||||
shinyApp <- function(ui=NULL, server=NULL, onStart=NULL, options=list(),
|
||||
uiPattern="/", enableBookmarking = NULL) {
|
||||
uiPattern="/", enableBookmarking=NULL) {
|
||||
if (is.null(server)) {
|
||||
stop("`server` missing from shinyApp")
|
||||
}
|
||||
@@ -212,7 +212,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
|
||||
if (file.exists(file.path.ci(appDir, "global.R")))
|
||||
sourceUTF8(file.path.ci(appDir, "global.R"))
|
||||
}
|
||||
onEnd <- function() {
|
||||
onStop <- function() {
|
||||
setwd(oldwd)
|
||||
monitorHandle()
|
||||
monitorHandle <<- NULL
|
||||
@@ -223,7 +223,7 @@ shinyAppDir_serverR <- function(appDir, options=list()) {
|
||||
httpHandler = joinHandlers(c(uiHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = serverFuncSource,
|
||||
onStart = onStart,
|
||||
onEnd = onEnd,
|
||||
onStop = onStop,
|
||||
options = options
|
||||
),
|
||||
class = "shiny.appobj"
|
||||
@@ -317,8 +317,9 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
monitorHandle <<- initAutoReloadMonitor(appDir)
|
||||
if (!is.null(appObj()$onStart)) appObj()$onStart()
|
||||
}
|
||||
onEnd <- function() {
|
||||
onStop <- function() {
|
||||
setwd(oldwd)
|
||||
monitorHandle()
|
||||
monitorHandle <<- NULL
|
||||
@@ -329,7 +330,7 @@ shinyAppDir_appR <- function(fileName, appDir, options=list())
|
||||
httpHandler = joinHandlers(c(dynHttpHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = dynServerFuncSource,
|
||||
onStart = onStart,
|
||||
onEnd = onEnd,
|
||||
onStop = onStop,
|
||||
options = options
|
||||
),
|
||||
class = "shiny.appobj"
|
||||
|
||||
@@ -349,7 +349,7 @@ RestoreContext <- R6Class("RestoreContext",
|
||||
mapply(names(vals), vals, SIMPLIFY = FALSE,
|
||||
FUN = function(name, value) {
|
||||
tryCatch(
|
||||
jsonlite::fromJSON(value),
|
||||
safeFromJSON(value),
|
||||
error = function(e) {
|
||||
stop("Failed to parse URL parameter \"", name, "\"")
|
||||
}
|
||||
|
||||
391
R/bootstrap.R
391
R/bootstrap.R
@@ -285,7 +285,8 @@ pageWithSidebar <- function(headerPanel,
|
||||
#' example below).
|
||||
#'
|
||||
#' @seealso \code{\link{tabPanel}}, \code{\link{tabsetPanel}},
|
||||
#' \code{\link{updateNavbarPage}}
|
||||
#' \code{\link{updateNavbarPage}}, \code{\link{insertTab}},
|
||||
#' \code{\link{showTab}}
|
||||
#'
|
||||
#' @examples
|
||||
#' navbarPage("App Title",
|
||||
@@ -393,10 +394,15 @@ navbarPage <- function(title,
|
||||
)
|
||||
}
|
||||
|
||||
#' @param menuName A name that identifies this \code{navbarMenu}. This
|
||||
#' is needed if you want to insert/remove or show/hide an entire
|
||||
#' \code{navbarMenu}.
|
||||
#'
|
||||
#' @rdname navbarPage
|
||||
#' @export
|
||||
navbarMenu <- function(title, ..., icon = NULL) {
|
||||
navbarMenu <- function(title, ..., menuName = title, icon = NULL) {
|
||||
structure(list(title = title,
|
||||
menuName = menuName,
|
||||
tabs = list(...),
|
||||
iconClass = iconClass(icon)),
|
||||
class = "shiny.navbarmenu")
|
||||
@@ -502,6 +508,8 @@ mainPanel <- function(..., width = 8) {
|
||||
#'
|
||||
#' @param condition A JavaScript expression that will be evaluated repeatedly to
|
||||
#' determine whether the panel should be displayed.
|
||||
#' @param ns The \code{\link[=NS]{namespace}} object of the current module, if
|
||||
#' any.
|
||||
#' @param ... Elements to include in the panel.
|
||||
#'
|
||||
#' @note You are not recommended to use special JavaScript characters such as a
|
||||
@@ -510,32 +518,55 @@ mainPanel <- function(..., width = 8) {
|
||||
#' \code{input["foo.bar"]} instead of \code{input.foo.bar} to read the input
|
||||
#' value.
|
||||
#' @examples
|
||||
#' sidebarPanel(
|
||||
#' selectInput(
|
||||
#' "plotType", "Plot Type",
|
||||
#' c(Scatter = "scatter",
|
||||
#' Histogram = "hist")),
|
||||
#'
|
||||
#' # Only show this panel if the plot type is a histogram
|
||||
#' conditionalPanel(
|
||||
#' condition = "input.plotType == 'hist'",
|
||||
#' selectInput(
|
||||
#' "breaks", "Breaks",
|
||||
#' c("Sturges",
|
||||
#' "Scott",
|
||||
#' "Freedman-Diaconis",
|
||||
#' "[Custom]" = "custom")),
|
||||
#'
|
||||
#' # Only show this panel if Custom is selected
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' ui <- fluidPage(
|
||||
#' sidebarPanel(
|
||||
#' selectInput("plotType", "Plot Type",
|
||||
#' c(Scatter = "scatter", Histogram = "hist")
|
||||
#' ),
|
||||
#' # Only show this panel if the plot type is a histogram
|
||||
#' conditionalPanel(
|
||||
#' condition = "input.breaks == 'custom'",
|
||||
#' sliderInput("breakCount", "Break Count", min=1, max=1000, value=10)
|
||||
#' condition = "input.plotType == 'hist'",
|
||||
#' selectInput(
|
||||
#' "breaks", "Breaks",
|
||||
#' c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
|
||||
#' ),
|
||||
#' # Only show this panel if Custom is selected
|
||||
#' conditionalPanel(
|
||||
#' condition = "input.breaks == 'custom'",
|
||||
#' sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#' ),
|
||||
#' mainPanel(
|
||||
#' plotOutput("plot")
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' x <- rnorm(100)
|
||||
#' y <- rnorm(100)
|
||||
#'
|
||||
#' output$plot <- renderPlot({
|
||||
#' if (input$plotType == "scatter") {
|
||||
#' plot(x, y)
|
||||
#' } else {
|
||||
#' breaks <- input$breaks
|
||||
#' if (breaks == "custom") {
|
||||
#' breaks <- input$breakCount
|
||||
#' }
|
||||
#'
|
||||
#' hist(x, breaks = breaks)
|
||||
#' }
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
conditionalPanel <- function(condition, ...) {
|
||||
div('data-display-if'=condition, ...)
|
||||
conditionalPanel <- function(condition, ..., ns = NS(NULL)) {
|
||||
div(`data-display-if`=condition, `data-ns-prefix`=ns(""), ...)
|
||||
}
|
||||
|
||||
#' Create a help text element
|
||||
@@ -609,7 +640,8 @@ tabPanel <- function(title, ..., value = title, icon = NULL) {
|
||||
#' Bootstrap 3.
|
||||
#' @return A tabset that can be passed to \code{\link{mainPanel}}
|
||||
#'
|
||||
#' @seealso \code{\link{tabPanel}}, \code{\link{updateTabsetPanel}}
|
||||
#' @seealso \code{\link{tabPanel}}, \code{\link{updateTabsetPanel}},
|
||||
#' \code{\link{insertTab}}, \code{\link{showTab}}
|
||||
#'
|
||||
#' @examples
|
||||
#' # Show a tabset that includes a plot, summary, and
|
||||
@@ -676,7 +708,9 @@ tabsetPanel <- function(...,
|
||||
#' supported. This is because version 0.11 switched to Bootstrap 3, which
|
||||
#' doesn't support separators.
|
||||
#'
|
||||
#' @seealso \code{\link{tabPanel}}, \code{\link{updateNavlistPanel}}
|
||||
#' @seealso \code{\link{tabPanel}}, \code{\link{updateNavlistPanel}},
|
||||
#' \code{\link{insertTab}}, \code{\link{showTab}}
|
||||
#'
|
||||
#' @examples
|
||||
#' fluidPage(
|
||||
#'
|
||||
@@ -726,189 +760,158 @@ navlistPanel <- function(...,
|
||||
fixedRow(columns)
|
||||
}
|
||||
|
||||
# Helpers to build tabsetPanels (& Co.) and their elements
|
||||
markTabAsSelected <- function(x) {
|
||||
attr(x, "selected") <- TRUE
|
||||
x
|
||||
}
|
||||
|
||||
buildTabset <- function(tabs, ulClass, textFilter = NULL,
|
||||
id = NULL, selected = NULL) {
|
||||
isTabSelected <- function(x) {
|
||||
isTRUE(attr(x, "selected", exact = TRUE))
|
||||
}
|
||||
|
||||
# This function proceeds in two phases. First, it scans over all the items
|
||||
# to find and mark which tab should start selected. Then it actually builds
|
||||
# the tab nav and tab content lists.
|
||||
containsSelectedTab <- function(tabs) {
|
||||
any(vapply(tabs, isTabSelected, logical(1)))
|
||||
}
|
||||
|
||||
# Mark an item as selected
|
||||
markSelected <- function(x) {
|
||||
attr(x, "selected") <- TRUE
|
||||
x
|
||||
}
|
||||
findAndMarkSelectedTab <- function(tabs, selected, foundSelected) {
|
||||
tabs <- lapply(tabs, function(div) {
|
||||
if (foundSelected || is.character(div)) {
|
||||
# Strings are not selectable items
|
||||
|
||||
# Returns TRUE if an item is selected
|
||||
isSelected <- function(x) {
|
||||
isTRUE(attr(x, "selected", exact = TRUE))
|
||||
}
|
||||
|
||||
# Returns TRUE if a list of tab items contains a selected tab, FALSE
|
||||
# otherwise.
|
||||
containsSelected <- function(tabs) {
|
||||
any(vapply(tabs, isSelected, logical(1)))
|
||||
}
|
||||
|
||||
# Take a pass over all tabs, and mark the selected tab.
|
||||
foundSelectedItem <- FALSE
|
||||
findAndMarkSelected <- function(tabs, selected) {
|
||||
lapply(tabs, function(divTag) {
|
||||
if (foundSelectedItem) {
|
||||
# If we already found the selected tab, no need to keep looking
|
||||
|
||||
} else if (is.character(divTag)) {
|
||||
# Strings don't represent selectable items
|
||||
|
||||
} else if (inherits(divTag, "shiny.navbarmenu")) {
|
||||
# Navbar menu
|
||||
divTag$tabs <- findAndMarkSelected(divTag$tabs, selected)
|
||||
} else if (inherits(div, "shiny.navbarmenu")) {
|
||||
# Recur for navbarMenus
|
||||
res <- findAndMarkSelectedTab(div$tabs, selected, foundSelected)
|
||||
div$tabs <- res$tabs
|
||||
foundSelected <<- res$foundSelected
|
||||
|
||||
} else {
|
||||
# Base case: regular tab item. If the `selected` argument is
|
||||
# provided, check for a match in the existing tabs; else,
|
||||
# mark first available item as selected
|
||||
if (is.null(selected)) {
|
||||
foundSelected <<- TRUE
|
||||
div <- markTabAsSelected(div)
|
||||
} else {
|
||||
# Regular tab item
|
||||
if (is.null(selected)) {
|
||||
# If selected tab isn't specified, mark first available item
|
||||
# as selected.
|
||||
foundSelectedItem <<- TRUE
|
||||
divTag <- markSelected(divTag)
|
||||
|
||||
} else {
|
||||
# If selected tab is specified, check for a match
|
||||
tabValue <- divTag$attribs$`data-value` %OR% divTag$attribs$title
|
||||
if (identical(selected, tabValue)) {
|
||||
foundSelectedItem <<- TRUE
|
||||
divTag <- markSelected(divTag)
|
||||
}
|
||||
tabValue <- div$attribs$`data-value` %OR% div$attribs$title
|
||||
if (identical(selected, tabValue)) {
|
||||
foundSelected <<- TRUE
|
||||
div <- markTabAsSelected(div)
|
||||
}
|
||||
}
|
||||
}
|
||||
return(div)
|
||||
})
|
||||
return(list(tabs = tabs, foundSelected = foundSelected))
|
||||
}
|
||||
|
||||
return(divTag)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
# Append an optional icon to an aTag
|
||||
appendIcon <- function(aTag, iconClass) {
|
||||
if (!is.null(iconClass)) {
|
||||
# Returns the icon object (or NULL if none), provided either a
|
||||
# tabPanel, OR the icon class
|
||||
getIcon <- function(tab = NULL, iconClass = NULL) {
|
||||
if (!is.null(tab)) iconClass <- tab$attribs$`data-icon-class`
|
||||
if (!is.null(iconClass)) {
|
||||
if (grepl("fa-", iconClass, fixed = TRUE)) {
|
||||
# for font-awesome we specify fixed-width
|
||||
if (grepl("fa-", iconClass, fixed = TRUE))
|
||||
iconClass <- paste(iconClass, "fa-fw")
|
||||
aTag <- tagAppendChild(aTag, icon(name = NULL, class = iconClass))
|
||||
iconClass <- paste(iconClass, "fa-fw")
|
||||
}
|
||||
aTag
|
||||
icon(name = NULL, class = iconClass)
|
||||
} else NULL
|
||||
}
|
||||
|
||||
# Text filter for navbarMenu's (plain text) separators
|
||||
navbarMenuTextFilter <- function(text) {
|
||||
if (grepl("^\\-+$", text)) tags$li(class = "divider")
|
||||
else tags$li(class = "dropdown-header", text)
|
||||
}
|
||||
|
||||
# This function is called internally by navbarPage, tabsetPanel
|
||||
# and navlistPanel
|
||||
buildTabset <- function(tabs, ulClass, textFilter = NULL, id = NULL,
|
||||
selected = NULL, foundSelected = FALSE) {
|
||||
|
||||
res <- findAndMarkSelectedTab(tabs, selected, foundSelected)
|
||||
tabs <- res$tabs
|
||||
foundSelected <- res$foundSelected
|
||||
|
||||
# add input class if we have an id
|
||||
if (!is.null(id)) ulClass <- paste(ulClass, "shiny-tab-input")
|
||||
|
||||
if (anyNamed(tabs)) {
|
||||
nms <- names(tabs)
|
||||
nms <- nms[nzchar(nms)]
|
||||
stop("Tabs should all be unnamed arguments, but some are named: ",
|
||||
paste(nms, collapse = ", "))
|
||||
}
|
||||
|
||||
# Build the tabset
|
||||
build <- function(tabs, ulClass, textFilter = NULL, id = NULL) {
|
||||
# add tab input sentinel class if we have an id
|
||||
if (!is.null(id))
|
||||
ulClass <- paste(ulClass, "shiny-tab-input")
|
||||
tabsetId <- p_randomInt(1000, 10000)
|
||||
tabs <- lapply(seq_len(length(tabs)), buildTabItem,
|
||||
tabsetId = tabsetId, foundSelected = foundSelected,
|
||||
tabs = tabs, textFilter = textFilter)
|
||||
|
||||
if (anyNamed(tabs)) {
|
||||
nms <- names(tabs)
|
||||
nms <- nms[nzchar(nms)]
|
||||
stop("Tabs should all be unnamed arguments, but some are named: ",
|
||||
paste(nms, collapse = ", "))
|
||||
tabNavList <- tags$ul(class = ulClass, id = id,
|
||||
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 1))
|
||||
|
||||
tabContent <- tags$div(class = "tab-content",
|
||||
`data-tabsetid` = tabsetId, lapply(tabs, "[[", 2))
|
||||
|
||||
list(navList = tabNavList, content = tabContent)
|
||||
}
|
||||
|
||||
# Builds tabPanel/navbarMenu items (this function used to be
|
||||
# declared inside the buildTabset() function and it's been
|
||||
# refactored for clarity and reusability). Called internally
|
||||
# by buildTabset.
|
||||
buildTabItem <- function(index, tabsetId, foundSelected, tabs = NULL,
|
||||
divTag = NULL, textFilter = NULL) {
|
||||
|
||||
divTag <- if (!is.null(divTag)) divTag else tabs[[index]]
|
||||
|
||||
if (is.character(divTag) && !is.null(textFilter)) {
|
||||
# text item: pass it to the textFilter if it exists
|
||||
liTag <- textFilter(divTag)
|
||||
divTag <- NULL
|
||||
|
||||
} else if (inherits(divTag, "shiny.navbarmenu")) {
|
||||
# navbarMenu item: build the child tabset
|
||||
tabset <- buildTabset(divTag$tabs, "dropdown-menu",
|
||||
navbarMenuTextFilter, foundSelected = foundSelected)
|
||||
|
||||
# if this navbarMenu contains a selected item, mark it active
|
||||
containsSelected <- containsSelectedTab(divTag$tabs)
|
||||
liTag <- tags$li(
|
||||
class = paste0("dropdown", if (containsSelected) " active"),
|
||||
tags$a(href = "#",
|
||||
class = "dropdown-toggle", `data-toggle` = "dropdown",
|
||||
`data-value` = divTag$menuName,
|
||||
divTag$title, tags$b(class = "caret"),
|
||||
getIcon(iconClass = divTag$iconClass)
|
||||
),
|
||||
tabset$navList # inner tabPanels items
|
||||
)
|
||||
# list of tab content divs from the child tabset
|
||||
divTag <- tabset$content$children
|
||||
|
||||
} else {
|
||||
# tabPanel item: create the tab's liTag and divTag
|
||||
tabId <- paste("tab", tabsetId, index, sep = "-")
|
||||
liTag <- tags$li(
|
||||
tags$a(
|
||||
href = paste("#", tabId, sep = ""),
|
||||
`data-toggle` = "tab",
|
||||
`data-value` = divTag$attribs$`data-value`,
|
||||
divTag$attribs$title,
|
||||
getIcon(iconClass = divTag$attribs$`data-icon-class`)
|
||||
)
|
||||
)
|
||||
# if this tabPanel is selected item, mark it active
|
||||
if (isTabSelected(divTag)) {
|
||||
liTag$attribs$class <- "active"
|
||||
divTag$attribs$class <- "tab-pane active"
|
||||
}
|
||||
|
||||
tabNavList <- tags$ul(class = ulClass, id = id)
|
||||
tabContent <- tags$div(class = "tab-content")
|
||||
tabsetId <- p_randomInt(1000, 10000)
|
||||
tabId <- 1
|
||||
|
||||
buildItem <- function(divTag) {
|
||||
# check for text; pass it to the textFilter or skip it if there is none
|
||||
if (is.character(divTag)) {
|
||||
if (!is.null(textFilter)) {
|
||||
tabNavList <<- tagAppendChild(tabNavList, textFilter(divTag))
|
||||
}
|
||||
|
||||
} else if (inherits(divTag, "shiny.navbarmenu")) {
|
||||
|
||||
# create the a tag
|
||||
aTag <- tags$a(href="#",
|
||||
class="dropdown-toggle",
|
||||
`data-toggle`="dropdown")
|
||||
|
||||
# add optional icon
|
||||
aTag <- appendIcon(aTag, divTag$iconClass)
|
||||
|
||||
# add the title and caret
|
||||
aTag <- tagAppendChild(aTag, divTag$title)
|
||||
aTag <- tagAppendChild(aTag, tags$b(class="caret"))
|
||||
|
||||
# build the dropdown list element
|
||||
liTag <- tags$li(class = "dropdown", aTag)
|
||||
|
||||
# text filter for separators
|
||||
textFilter <- function(text) {
|
||||
if (grepl("^\\-+$", text))
|
||||
tags$li(class="divider")
|
||||
else
|
||||
tags$li(class="dropdown-header", text)
|
||||
}
|
||||
|
||||
# build the child tabset
|
||||
tabset <- build(divTag$tabs, "dropdown-menu", textFilter)
|
||||
liTag <- tagAppendChild(liTag, tabset$navList)
|
||||
|
||||
# If this navbar menu contains a selected item, mark it as active
|
||||
if (containsSelected(divTag$tabs)) {
|
||||
liTag$attribs$class <- paste(liTag$attribs$class, "active")
|
||||
}
|
||||
|
||||
tabNavList <<- tagAppendChild(tabNavList, liTag)
|
||||
# don't add a standard tab content div, rather add the list of tab
|
||||
# content divs that are contained within the tabset
|
||||
tabContent <<- tagAppendChildren(tabContent,
|
||||
list = tabset$content$children)
|
||||
|
||||
} else {
|
||||
# Standard navbar item
|
||||
# compute id and assign it to the div
|
||||
thisId <- paste("tab", tabsetId, tabId, sep="-")
|
||||
divTag$attribs$id <- thisId
|
||||
tabId <<- tabId + 1
|
||||
|
||||
tabValue <- divTag$attribs$`data-value`
|
||||
|
||||
# create the a tag
|
||||
aTag <- tags$a(href=paste("#", thisId, sep=""),
|
||||
`data-toggle` = "tab",
|
||||
`data-value` = tabValue)
|
||||
|
||||
# append optional icon
|
||||
aTag <- appendIcon(aTag, divTag$attribs$`data-icon-class`)
|
||||
|
||||
# add the title
|
||||
aTag <- tagAppendChild(aTag, divTag$attribs$title)
|
||||
|
||||
# create the li tag
|
||||
liTag <- tags$li(aTag)
|
||||
|
||||
# If selected, set appropriate classes on li tag and div tag.
|
||||
if (isSelected(divTag)) {
|
||||
liTag$attribs$class <- "active"
|
||||
divTag$attribs$class <- "tab-pane active"
|
||||
}
|
||||
|
||||
divTag$attribs$title <- NULL
|
||||
|
||||
# append the elements to our lists
|
||||
tabNavList <<- tagAppendChild(tabNavList, liTag)
|
||||
tabContent <<- tagAppendChild(tabContent, divTag)
|
||||
}
|
||||
}
|
||||
|
||||
lapply(tabs, buildItem)
|
||||
list(navList = tabNavList, content = tabContent)
|
||||
divTag$attribs$id <- tabId
|
||||
divTag$attribs$title <- NULL
|
||||
}
|
||||
|
||||
|
||||
# Finally, actually invoke the functions to do the processing.
|
||||
tabs <- findAndMarkSelected(tabs, selected)
|
||||
build(tabs, ulClass, textFilter, id)
|
||||
return(list(liTag = liTag, divTag = divTag))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,18 @@
|
||||
# form upload, i.e. traditional HTTP POST-based file upload) doesn't work with
|
||||
# the websockets package's HTTP server at the moment.
|
||||
|
||||
# @description Returns a file's extension, with a leading dot, if one can be
|
||||
# found. A valid extension contains only alphanumeric characters. If there is
|
||||
# no extension, or if it contains non-alphanumeric characters, an empty
|
||||
# string is returned.
|
||||
# @param x character vector giving file paths.
|
||||
# @return The extension of \code{x}, with a leading dot, if one was found.
|
||||
# Otherwise, an empty character vector.
|
||||
maybeGetExtension <- function(x) {
|
||||
ext <- tools::file_ext(x)
|
||||
ifelse(ext == "", ext, paste0(".", ext))
|
||||
}
|
||||
|
||||
FileUploadOperation <- R6Class(
|
||||
'FileUploadOperation',
|
||||
portable = FALSE,
|
||||
@@ -52,8 +64,9 @@ FileUploadOperation <- R6Class(
|
||||
.currentFileInfo <<- file
|
||||
.pendingFileInfos <<- tail(.pendingFileInfos, -1)
|
||||
|
||||
filename <- file.path(.dir, as.character(length(.files$name)))
|
||||
row <- data.frame(name=file$name, size=file$size, type=file$type,
|
||||
fileBasename <- basename(.currentFileInfo$name)
|
||||
filename <- file.path(.dir, paste0(as.character(length(.files$name)), maybeGetExtension(fileBasename)))
|
||||
row <- data.frame(name=fileBasename, size=file$size, type=file$type,
|
||||
datapath=filename, stringsAsFactors=FALSE)
|
||||
|
||||
if (length(.files$name) == 0)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# R's lazy-loading package scheme causes the private seed to be cached in the
|
||||
# package itself, making our PRNG completely deterministic. This line resets
|
||||
# the private seed during load.
|
||||
withPrivateSeed(reinitializeSeed())
|
||||
withPrivateSeed(set.seed(NULL))
|
||||
}
|
||||
|
||||
.onAttach <- function(libname, pkgname) {
|
||||
|
||||
@@ -6,13 +6,18 @@
|
||||
#' URL.
|
||||
#'
|
||||
#' @param dependency A single HTML dependency object, created using
|
||||
#' \code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named, then
|
||||
#' \code{href} and/or \code{file} names must be present.
|
||||
#' \code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named,
|
||||
#' then \code{href} and/or \code{file} names must be present.
|
||||
#' @param scrubFile If TRUE (the default), remove \code{src$file} for the
|
||||
#' dependency. This prevents the local file path from being sent to the client
|
||||
#' when dynamic web dependencies are used. If FALSE, don't remove
|
||||
#' \code{src$file}. Setting it to FALSE should be needed only in very unusual
|
||||
#' cases.
|
||||
#'
|
||||
#' @return A single HTML dependency object that has an \code{href}-named element
|
||||
#' in its \code{src}.
|
||||
#' @export
|
||||
createWebDependency <- function(dependency) {
|
||||
createWebDependency <- function(dependency, scrubFile = TRUE) {
|
||||
if (is.null(dependency))
|
||||
return(NULL)
|
||||
|
||||
@@ -25,6 +30,10 @@ createWebDependency <- function(dependency) {
|
||||
dependency$src$href <- prefix
|
||||
}
|
||||
|
||||
# Don't leak local file path to client
|
||||
if (scrubFile)
|
||||
dependency$src$file <- NULL
|
||||
|
||||
return(dependency)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#' @param choices List of values to show checkboxes for. If elements of the list
|
||||
#' are named then that name rather than the value is displayed to the user. If
|
||||
#' this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
#' must not be provided, and vice-versa.
|
||||
#' must not be provided, and vice-versa. The values should be strings; other
|
||||
#' types (such as logicals and numbers) will be coerced to strings.
|
||||
#' @param selected The values that should be initially selected, if any.
|
||||
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
|
||||
#' @param choiceNames,choiceValues List of names and values, respectively,
|
||||
|
||||
@@ -3,30 +3,30 @@
|
||||
#' Create a set of radio buttons used to select an item from a list.
|
||||
#'
|
||||
#' If you need to represent a "None selected" state, it's possible to default
|
||||
#' the radio buttons to have no options selected by using
|
||||
#' \code{selected = character(0)}. However, this is not recommended, as it gives
|
||||
#' the user no way to return to that state once they've made a selection.
|
||||
#' Instead, consider having the first of your choices be \code{c("None selected"
|
||||
#' = "")}.
|
||||
#' the radio buttons to have no options selected by using \code{selected =
|
||||
#' character(0)}. However, this is not recommended, as it gives the user no way
|
||||
#' to return to that state once they've made a selection. Instead, consider
|
||||
#' having the first of your choices be \code{c("None selected" = "")}.
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to select from (if elements of the list are
|
||||
#' named then that name rather than the value is displayed to the user). If
|
||||
#' this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
#' must not be provided, and vice-versa.
|
||||
#' @param selected The initially selected value (if not specified then
|
||||
#' defaults to the first value)
|
||||
#' must not be provided, and vice-versa. The values should be strings; other
|
||||
#' types (such as logicals and numbers) will be coerced to strings.
|
||||
#' @param selected The initially selected value (if not specified then defaults
|
||||
#' to the first value)
|
||||
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
|
||||
#' @return A set of radio buttons that can be added to a UI definition.
|
||||
#' @param choiceNames,choiceValues List of names and values, respectively,
|
||||
#' that are displayed to the user in the app and correspond to the each
|
||||
#' choice (for this reason, \code{choiceNames} and \code{choiceValues}
|
||||
#' must have the same length). If either of these arguments is
|
||||
#' provided, then the other \emph{must} be provided and \code{choices}
|
||||
#' \emph{must not} be provided. The advantage of using both of these over
|
||||
#' a named list for \code{choices} is that \code{choiceNames} allows any
|
||||
#' type of UI object to be passed through (tag objects, icons, HTML code,
|
||||
#' ...), instead of just simple text. See Examples.
|
||||
#' @param choiceNames,choiceValues List of names and values, respectively, that
|
||||
#' are displayed to the user in the app and correspond to the each choice (for
|
||||
#' this reason, \code{choiceNames} and \code{choiceValues} must have the same
|
||||
#' length). If either of these arguments is provided, then the other
|
||||
#' \emph{must} be provided and \code{choices} \emph{must not} be provided. The
|
||||
#' advantage of using both of these over a named list for \code{choices} is
|
||||
#' that \code{choiceNames} allows any type of UI object to be passed through
|
||||
#' (tag objects, icons, HTML code, ...), instead of just simple text. See
|
||||
#' Examples.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateRadioButtons}}
|
||||
|
||||
@@ -73,10 +73,10 @@ generateOptions <- function(inputId, selected, inline, type = 'checkbox',
|
||||
# checkbox-inline.
|
||||
if (inline) {
|
||||
tags$label(class = paste0(type, "-inline"), inputTag,
|
||||
tags$span(pd$html, pd$dep))
|
||||
tags$span(pd$html, pd$deps))
|
||||
} else {
|
||||
tags$div(class = type, tags$label(inputTag,
|
||||
tags$span(pd$html, pd$dep)))
|
||||
tags$span(pd$html, pd$deps)))
|
||||
}
|
||||
},
|
||||
SIMPLIFY = FALSE, USE.NAMES = FALSE
|
||||
|
||||
325
R/insert-tab.R
Normal file
325
R/insert-tab.R
Normal file
@@ -0,0 +1,325 @@
|
||||
#' Dynamically insert/remove a tabPanel
|
||||
#'
|
||||
#' Dynamically insert or remove a \code{\link{tabPanel}} (or a
|
||||
#' \code{\link{navbarMenu}}) from an existing \code{\link{tabsetPanel}},
|
||||
#' \code{\link{navlistPanel}} or \code{\link{navbarPage}}.
|
||||
#'
|
||||
#' When you want to insert a new tab before or after an existing tab, you
|
||||
#' should use \code{insertTab}. When you want to prepend a tab (i.e. add a
|
||||
#' tab to the beginning of the \code{tabsetPanel}), use \code{prependTab}.
|
||||
#' When you want to append a tab (i.e. add a tab to the end of the
|
||||
#' \code{tabsetPanel}), use \code{appendTab}.
|
||||
#'
|
||||
#' For \code{navbarPage}, you can insert/remove conventional
|
||||
#' \code{tabPanel}s (whether at the top level or nested inside a
|
||||
#' \code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
|
||||
#' For the latter case, \code{target} should be the \code{menuName} that
|
||||
#' you gave your \code{navbarMenu} when you first created it (by default,
|
||||
#' this is equal to the value of the \code{title} argument).
|
||||
#'
|
||||
#' @param inputId The \code{id} of the \code{tabsetPanel} (or
|
||||
#' \code{navlistPanel} or \code{navbarPage}) into which \code{tab} will
|
||||
#' be inserted/removed.
|
||||
#'
|
||||
#' @param tab The item to be added (must be created with \code{tabPanel},
|
||||
#' or with \code{navbarMenu}).
|
||||
#'
|
||||
#' @param target If inserting: the \code{value} of an existing
|
||||
#' \code{tabPanel}, next to which \code{tab} will be added.
|
||||
#' If removing: the \code{value} of the \code{tabPanel} that
|
||||
#' you want to remove. See Details if you want to insert next to/remove
|
||||
#' an entire \code{navbarMenu} instead.
|
||||
#'
|
||||
#' @param position Should \code{tab} be added before or after the
|
||||
#' \code{target} tab?
|
||||
#'
|
||||
#' @param select Should \code{tab} be selected upon being inserted?
|
||||
#'
|
||||
#' @param session The shiny session within which to call this function.
|
||||
#'
|
||||
#' @seealso \code{\link{showTab}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' # example app for inserting/removing a tab
|
||||
#' ui <- fluidPage(
|
||||
#' sidebarLayout(
|
||||
#' sidebarPanel(
|
||||
#' actionButton("add", "Add 'Dynamic' tab"),
|
||||
#' actionButton("remove", "Remove 'Foo' tab")
|
||||
#' ),
|
||||
#' mainPanel(
|
||||
#' tabsetPanel(id = "tabs",
|
||||
#' tabPanel("Hello", "This is the hello tab"),
|
||||
#' tabPanel("Foo", "This is the foo tab"),
|
||||
#' tabPanel("Bar", "This is the bar tab")
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#' )
|
||||
#' server <- function(input, output, session) {
|
||||
#' observeEvent(input$add, {
|
||||
#' insertTab(inputId = "tabs",
|
||||
#' tabPanel("Dynamic", "This a dynamically-added tab"),
|
||||
#' target = "Bar"
|
||||
#' )
|
||||
#' })
|
||||
#' observeEvent(input$remove, {
|
||||
#' removeTab(inputId = "tabs", target = "Foo")
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#'
|
||||
#' # example app for prepending/appending a navbarMenu
|
||||
#' ui <- navbarPage("Navbar page", id = "tabs",
|
||||
#' tabPanel("Home",
|
||||
#' actionButton("prepend", "Prepend a navbarMenu"),
|
||||
#' actionButton("append", "Append a navbarMenu")
|
||||
#' )
|
||||
#' )
|
||||
#' server <- function(input, output, session) {
|
||||
#' observeEvent(input$prepend, {
|
||||
#' id <- paste0("Dropdown", input$prepend, "p")
|
||||
#' prependTab(inputId = "tabs",
|
||||
#' navbarMenu(id,
|
||||
#' tabPanel("Drop1", paste("Drop1 page from", id)),
|
||||
#' tabPanel("Drop2", paste("Drop2 page from", id)),
|
||||
#' "------",
|
||||
#' "Header",
|
||||
#' tabPanel("Drop3", paste("Drop3 page from", id))
|
||||
#' )
|
||||
#' )
|
||||
#' })
|
||||
#' observeEvent(input$append, {
|
||||
#' id <- paste0("Dropdown", input$append, "a")
|
||||
#' appendTab(inputId = "tabs",
|
||||
#' navbarMenu(id,
|
||||
#' tabPanel("Drop1", paste("Drop1 page from", id)),
|
||||
#' tabPanel("Drop2", paste("Drop2 page from", id)),
|
||||
#' "------",
|
||||
#' "Header",
|
||||
#' tabPanel("Drop3", paste("Drop3 page from", id))
|
||||
#' )
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' }
|
||||
#' @export
|
||||
insertTab <- function(inputId, tab, target,
|
||||
position = c("before", "after"), select = FALSE,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(target)
|
||||
force(select)
|
||||
position <- match.arg(position)
|
||||
inputId <- session$ns(inputId)
|
||||
|
||||
# Barbara -- August 2017
|
||||
# Note: until now, the number of tabs in a tabsetPanel (or navbarPage
|
||||
# or navlistPanel) was always fixed. So, an easy way to give an id to
|
||||
# a tab was simply incrementing a counter. (Just like it was easy to
|
||||
# give a random 4-digit number to identify the tabsetPanel). Since we
|
||||
# can only know this in the client side, we'll just pass `id` and
|
||||
# `tsid` (TabSetID) as dummy values that will be fixed in the JS code.
|
||||
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
|
||||
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
|
||||
|
||||
callback <- function() {
|
||||
session$sendInsertTab(
|
||||
inputId = inputId,
|
||||
liTag = processDeps(item$liTag, session),
|
||||
divTag = processDeps(item$divTag, session),
|
||||
menuName = NULL,
|
||||
target = target,
|
||||
position = position,
|
||||
select = select)
|
||||
}
|
||||
session$onFlush(callback, once = TRUE)
|
||||
}
|
||||
|
||||
#' @param menuName This argument should only be used when you want to
|
||||
#' prepend (or append) \code{tab} to the beginning (or end) of an
|
||||
#' existing \code{\link{navbarMenu}} (which must itself be part of
|
||||
#' an existing \code{\link{navbarPage}}). In this case, this argument
|
||||
#' should be the \code{menuName} that you gave your \code{navbarMenu}
|
||||
#' when you first created it (by default, this is equal to the value
|
||||
#' of the \code{title} argument). Note that you still need to set the
|
||||
#' \code{inputId} argument to whatever the \code{id} of the parent
|
||||
#' \code{navbarPage} is. If \code{menuName} is left as \code{NULL},
|
||||
#' \code{tab} will be prepended (or appended) to whatever
|
||||
#' \code{inputId} is.
|
||||
#'
|
||||
#' @rdname insertTab
|
||||
#' @export
|
||||
prependTab <- function(inputId, tab, select = FALSE, menuName = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(select)
|
||||
force(menuName)
|
||||
inputId <- session$ns(inputId)
|
||||
|
||||
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
|
||||
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
|
||||
|
||||
callback <- function() {
|
||||
session$sendInsertTab(
|
||||
inputId = inputId,
|
||||
liTag = processDeps(item$liTag, session),
|
||||
divTag = processDeps(item$divTag, session),
|
||||
menuName = menuName,
|
||||
target = NULL,
|
||||
position = "after",
|
||||
select = select)
|
||||
}
|
||||
session$onFlush(callback, once = TRUE)
|
||||
}
|
||||
|
||||
#' @rdname insertTab
|
||||
#' @export
|
||||
appendTab <- function(inputId, tab, select = FALSE, menuName = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(select)
|
||||
force(menuName)
|
||||
inputId <- session$ns(inputId)
|
||||
|
||||
item <- buildTabItem("id", "tsid", TRUE, divTag = tab,
|
||||
textFilter = if (is.character(tab)) navbarMenuTextFilter else NULL)
|
||||
|
||||
callback <- function() {
|
||||
session$sendInsertTab(
|
||||
inputId = inputId,
|
||||
liTag = processDeps(item$liTag, session),
|
||||
divTag = processDeps(item$divTag, session),
|
||||
menuName = menuName,
|
||||
target = NULL,
|
||||
position = "before",
|
||||
select = select)
|
||||
}
|
||||
session$onFlush(callback, once = TRUE)
|
||||
}
|
||||
|
||||
#' @rdname insertTab
|
||||
#' @export
|
||||
removeTab <- function(inputId, target,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(target)
|
||||
inputId <- session$ns(inputId)
|
||||
|
||||
callback <- function() {
|
||||
session$sendRemoveTab(
|
||||
inputId = inputId,
|
||||
target = target)
|
||||
}
|
||||
session$onFlush(callback, once = TRUE)
|
||||
}
|
||||
|
||||
|
||||
#' Dynamically hide/show a tabPanel
|
||||
#'
|
||||
#' Dynamically hide or show a \code{\link{tabPanel}} (or a
|
||||
#' \code{\link{navbarMenu}})from an existing \code{\link{tabsetPanel}},
|
||||
#' \code{\link{navlistPanel}} or \code{\link{navbarPage}}.
|
||||
#'
|
||||
#' For \code{navbarPage}, you can hide/show conventional
|
||||
#' \code{tabPanel}s (whether at the top level or nested inside a
|
||||
#' \code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
|
||||
#' For the latter case, \code{target} should be the \code{menuName} that
|
||||
#' you gave your \code{navbarMenu} when you first created it (by default,
|
||||
#' this is equal to the value of the \code{title} argument).
|
||||
#'
|
||||
#' @param inputId The \code{id} of the \code{tabsetPanel} (or
|
||||
#' \code{navlistPanel} or \code{navbarPage}) in which to find
|
||||
#' \code{target}.
|
||||
#'
|
||||
#' @param target The \code{value} of the \code{tabPanel} to be
|
||||
#' hidden/shown. See Details if you want to hide/show an entire
|
||||
#' \code{navbarMenu} instead.
|
||||
#'
|
||||
#' @param select Should \code{target} be selected upon being shown?
|
||||
#'
|
||||
#' @param session The shiny session within which to call this function.
|
||||
#'
|
||||
#' @seealso \code{\link{insertTab}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- navbarPage("Navbar page", id = "tabs",
|
||||
#' tabPanel("Home",
|
||||
#' actionButton("hideTab", "Hide 'Foo' tab"),
|
||||
#' actionButton("showTab", "Show 'Foo' tab"),
|
||||
#' actionButton("hideMenu", "Hide 'More' navbarMenu"),
|
||||
#' actionButton("showMenu", "Show 'More' navbarMenu")
|
||||
#' ),
|
||||
#' tabPanel("Foo", "This is the foo tab"),
|
||||
#' tabPanel("Bar", "This is the bar tab"),
|
||||
#' navbarMenu("More",
|
||||
#' tabPanel("Table", "Table page"),
|
||||
#' tabPanel("About", "About page"),
|
||||
#' "------",
|
||||
#' "Even more!",
|
||||
#' tabPanel("Email", "Email page")
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' observeEvent(input$hideTab, {
|
||||
#' hideTab(inputId = "tabs", target = "Foo")
|
||||
#' })
|
||||
#'
|
||||
#' observeEvent(input$showTab, {
|
||||
#' showTab(inputId = "tabs", target = "Foo")
|
||||
#' })
|
||||
#'
|
||||
#' observeEvent(input$hideMenu, {
|
||||
#' hideTab(inputId = "tabs", target = "More")
|
||||
#' })
|
||||
#'
|
||||
#' observeEvent(input$showMenu, {
|
||||
#' showTab(inputId = "tabs", target = "More")
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
showTab <- function(inputId, target, select = FALSE,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(target)
|
||||
|
||||
if (select) updateTabsetPanel(session, inputId, selected = target)
|
||||
inputId <- session$ns(inputId)
|
||||
|
||||
callback <- function() {
|
||||
session$sendChangeTabVisibility(
|
||||
inputId = inputId,
|
||||
target = target,
|
||||
type = "show"
|
||||
)
|
||||
}
|
||||
session$onFlush(callback, once = TRUE)
|
||||
}
|
||||
|
||||
#' @rdname showTab
|
||||
#' @export
|
||||
hideTab <- function(inputId, target,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
force(target)
|
||||
inputId <- session$ns(inputId)
|
||||
|
||||
callback <- function() {
|
||||
session$sendChangeTabVisibility(
|
||||
inputId = inputId,
|
||||
target = target,
|
||||
type = "hide"
|
||||
)
|
||||
}
|
||||
session$onFlush(callback, once = TRUE)
|
||||
}
|
||||
@@ -360,7 +360,9 @@ HandlerManager <- R6Class("HandlerManager",
|
||||
|
||||
response <- filter(req, response)
|
||||
if (head_request) {
|
||||
headers$`Content-Length` <- nchar(response$content, type = "bytes")
|
||||
|
||||
headers$`Content-Length` <- getResponseContentLength(response, deleteOwnedContent = TRUE)
|
||||
|
||||
return(list(
|
||||
status = response$status,
|
||||
body = "",
|
||||
@@ -383,6 +385,35 @@ HandlerManager <- R6Class("HandlerManager",
|
||||
)
|
||||
)
|
||||
|
||||
# Safely get the Content-Length of a Rook response, or NULL if the length cannot
|
||||
# be determined for whatever reason (probably malformed response$content).
|
||||
# If deleteOwnedContent is TRUE, then the function should delete response
|
||||
# content that is of the form list(file=..., owned=TRUE).
|
||||
getResponseContentLength <- function(response, deleteOwnedContent) {
|
||||
force(deleteOwnedContent)
|
||||
|
||||
result <- if (is.character(response$content) && length(response$content) == 1) {
|
||||
nchar(response$content, type = "bytes")
|
||||
} else if (is.raw(response$content)) {
|
||||
length(response$content)
|
||||
} else if (is.list(response$content) && !is.null(response$content$file)) {
|
||||
if (deleteOwnedContent && isTRUE(response$content$owned)) {
|
||||
on.exit(unlink(response$content$file, recursive = FALSE, force = FALSE), add = TRUE)
|
||||
}
|
||||
file.info(response$content$file)$size
|
||||
} else {
|
||||
warning("HEAD request for unexpected content class ", class(response$content)[[1]])
|
||||
NULL
|
||||
}
|
||||
|
||||
if (is.na(result)) {
|
||||
# Mostly for missing file case
|
||||
return(NULL)
|
||||
} else {
|
||||
return(result)
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# ## Next steps
|
||||
#
|
||||
|
||||
@@ -26,6 +26,11 @@ createSessionProxy <- function(parentSession, ...) {
|
||||
|
||||
#' @export
|
||||
`$<-.session_proxy` <- function(x, name, value) {
|
||||
# this line allows users to write into session$userData
|
||||
# (e.g. it allows something like `session$userData$x <- TRUE`,
|
||||
# but not `session$userData <- TRUE`) from within a module
|
||||
# without any hacks (see PR #1732)
|
||||
if (identical(x[[name]], value)) return(x)
|
||||
stop("Attempted to assign value on session proxy.")
|
||||
}
|
||||
|
||||
|
||||
@@ -47,12 +47,13 @@ ReactiveVal <- R6Class(
|
||||
value = NULL,
|
||||
label = NULL,
|
||||
frozen = FALSE,
|
||||
dependents = Dependents$new()
|
||||
dependents = NULL
|
||||
),
|
||||
public = list(
|
||||
initialize = function(value, label = NULL) {
|
||||
private$value <- value
|
||||
private$label <- label
|
||||
private$dependents <- Dependents$new()
|
||||
.graphValueChange(private$label, value)
|
||||
},
|
||||
get = function() {
|
||||
@@ -1541,9 +1542,22 @@ coerceToFunc <- function(x) {
|
||||
#' @seealso \code{\link{reactiveFileReader}}
|
||||
#'
|
||||
#' @examples
|
||||
#' # Assume the existence of readTimestamp and readValue functions
|
||||
#' function(input, output, session) {
|
||||
#' data <- reactivePoll(1000, session, readTimestamp, readValue)
|
||||
#'
|
||||
#' data <- reactivePoll(1000, session,
|
||||
#' # This function returns the time that log_file was last modified
|
||||
#' checkFunc = function() {
|
||||
#' if (file.exists(log_file))
|
||||
#' file.info(log_file)$mtime[1]
|
||||
#' else
|
||||
#' ""
|
||||
#' },
|
||||
#' # This function returns the content of log_file
|
||||
#' valueFunc = function() {
|
||||
#' read.csv(log_file)
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' output$dataTable <- renderTable({
|
||||
#' data()
|
||||
#' })
|
||||
|
||||
@@ -470,11 +470,13 @@ find_panel_info <- function(b) {
|
||||
# This is for ggplot2>2.2.1, after an API was introduced for extracting
|
||||
# information about the plot object.
|
||||
find_panel_info_api <- function(b) {
|
||||
# Workaround for check NOTE, until ggplot2 >2.2.1 is released
|
||||
colon_colon <- `::`
|
||||
# Given a built ggplot object, return x and y domains (data space coords) for
|
||||
# each panel.
|
||||
layout <- ggplot2::summarise_layout(b)
|
||||
coord <- ggplot2::summarise_coord(b)
|
||||
layers <- ggplot2::summarise_layers(b)
|
||||
layout <- colon_colon("ggplot2", "summarise_layout")(b)
|
||||
coord <- colon_colon("ggplot2", "summarise_coord")(b)
|
||||
layers <- colon_colon("ggplot2", "summarise_layers")(b)
|
||||
|
||||
# Given x and y scale objects and a coord object, return a list that has
|
||||
# the bases of log transformations for x and y, or NULL if it's not a
|
||||
|
||||
@@ -176,7 +176,12 @@ renderTable <- function(expr, striped = FALSE, hover = FALSE,
|
||||
else ""
|
||||
}, " ",
|
||||
"class = '", htmlEscape(classNames, TRUE), "' ",
|
||||
"style = 'width:", validateCssUnit(width), ";'"))
|
||||
"style = 'width:", validateCssUnit(width), ";'"),
|
||||
comment = {
|
||||
if ("comment" %in% names(dots)) dots$comment
|
||||
else FALSE
|
||||
}
|
||||
)
|
||||
|
||||
print_args <- c(print_args, non_xtable_args)
|
||||
print_args <- print_args[unique(names(print_args))]
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
#' Add a function for serializing an input before bookmarking application state
|
||||
#'
|
||||
#' @param inputId Name of the input value.
|
||||
#' @param fun A function that takes the input value and returns a modified
|
||||
#' value. The returned value will be used for the test snapshot.
|
||||
#' @param session A Shiny session object.
|
||||
#'
|
||||
#' @keywords internal
|
||||
#' @export
|
||||
setSerializer <- function(inputId, fun, session = getDefaultReactiveDomain()) {
|
||||
if (is.null(session)) {
|
||||
stop("setSerializer() needs a session object.")
|
||||
}
|
||||
|
||||
input_impl <- .subset2(session$input, "impl")
|
||||
input_impl$setMeta(inputId, "shiny.serializer", fun)
|
||||
}
|
||||
|
||||
|
||||
# For most types of values, simply return the value unchanged.
|
||||
serializerDefault <- function(value, stateDir) {
|
||||
value
|
||||
@@ -58,12 +77,12 @@ serializeReactiveValues <- function(values, exclude, stateDir = NULL) {
|
||||
|
||||
# Get the serializer function for this input value. If none specified, use
|
||||
# the default.
|
||||
serializer <- impl$getMeta(name, "shiny.serializer")
|
||||
if (is.null(serializer))
|
||||
serializer <- serializerDefault
|
||||
serializer_fun <- impl$getMeta(name, "shiny.serializer")
|
||||
if (is.null(serializer_fun))
|
||||
serializer_fun <- serializerDefault
|
||||
|
||||
# Apply serializer function.
|
||||
serializer(val, stateDir)
|
||||
serializer_fun(val, stateDir)
|
||||
})
|
||||
|
||||
# Filter out any values that were marked as unserializable.
|
||||
|
||||
@@ -148,7 +148,7 @@ registerInputHandler("shiny.number", function(val, ...){
|
||||
|
||||
registerInputHandler("shiny.password", function(val, shinysession, name) {
|
||||
# Mark passwords as not serializable
|
||||
.subset2(shinysession$input, "impl")$setMeta(name, "shiny.serializer", serializerUnserializable)
|
||||
setSerializer(name, serializerUnserializable)
|
||||
val
|
||||
})
|
||||
|
||||
@@ -214,7 +214,9 @@ registerInputHandler("shiny.file", function(val, shinysession, name) {
|
||||
# Need to mark this input value with the correct serializer. When a file is
|
||||
# uploaded the usual way (instead of being restored), this occurs in
|
||||
# session$`@uploadEnd`.
|
||||
.subset2(shinysession$input, "impl")$setMeta(name, "shiny.serializer", serializerFileInput)
|
||||
setSerializer(name, serializerFileInput)
|
||||
|
||||
snapshotPreprocessInput(name, snapshotPreprocessorFileInput)
|
||||
|
||||
val
|
||||
})
|
||||
|
||||
49
R/server.R
49
R/server.R
@@ -155,7 +155,7 @@ decodeMessage <- function(data) {
|
||||
# Treat message as UTF-8
|
||||
charData <- rawToChar(data)
|
||||
Encoding(charData) <- 'UTF-8'
|
||||
return(jsonlite::fromJSON(charData, simplifyVector=FALSE))
|
||||
return(safeFromJSON(charData, simplifyVector=FALSE))
|
||||
}
|
||||
|
||||
i <- 5
|
||||
@@ -226,7 +226,7 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
message("RECV ", rawToChar(msg))
|
||||
}
|
||||
|
||||
if (identical(charToRaw("\003\xe9"), msg))
|
||||
if (isEmptyMessage(msg))
|
||||
return()
|
||||
|
||||
msg <- decodeMessage(msg)
|
||||
@@ -370,9 +370,9 @@ argsForServerFunc <- function(serverFunc, session) {
|
||||
}
|
||||
|
||||
getEffectiveBody <- function(func) {
|
||||
# Note: NULL values are OK. isS4(NULL) returns FALSE, body(NULL)
|
||||
# returns NULL.
|
||||
if (isS4(func) && class(func) == "functionWithTrace")
|
||||
if (is.null(func))
|
||||
NULL
|
||||
else if (isS4(func) && class(func) == "functionWithTrace")
|
||||
body(func@original)
|
||||
else
|
||||
body(func)
|
||||
@@ -465,6 +465,17 @@ serviceApp <- function() {
|
||||
# Global flag that's TRUE whenever we're inside of the scope of a call to runApp
|
||||
.globals$running <- FALSE
|
||||
|
||||
#' Check whether a Shiny application is running
|
||||
#'
|
||||
#' This function tests whether a Shiny application is currently running.
|
||||
#'
|
||||
#' @return \code{TRUE} if a Shiny application is currently running. Otherwise,
|
||||
#' \code{FALSE}.
|
||||
#' @export
|
||||
isRunning <- function() {
|
||||
.globals$running
|
||||
}
|
||||
|
||||
#' Run Shiny Application
|
||||
#'
|
||||
#' Runs a Shiny application. This function normally does not return; interrupt R
|
||||
@@ -577,7 +588,11 @@ runApp <- function(appDir=getwd(),
|
||||
|
||||
# Make warnings print immediately
|
||||
# Set pool.scheduler to support pool package
|
||||
ops <- options(warn = 1, pool.scheduler = scheduleTask)
|
||||
ops <- options(
|
||||
# Raise warn level to 1, but don't lower it
|
||||
warn = max(1, getOption("warn", default = 1)),
|
||||
pool.scheduler = scheduleTask
|
||||
)
|
||||
on.exit(options(ops), add = TRUE)
|
||||
|
||||
appParts <- as.shiny.appobj(appDir)
|
||||
@@ -716,7 +731,8 @@ runApp <- function(appDir=getwd(),
|
||||
port <- p_randomInt(3000, 8000)
|
||||
# Reject ports in this range that are considered unsafe by Chrome
|
||||
# http://superuser.com/questions/188058/which-ports-are-considered-unsafe-on-chrome
|
||||
if (!port %in% c(3659, 4045, 6000, 6665:6669)) {
|
||||
# https://github.com/rstudio/shiny/issues/1784
|
||||
if (!port %in% c(3659, 4045, 6000, 6665:6669, 6697)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -732,15 +748,22 @@ runApp <- function(appDir=getwd(),
|
||||
}
|
||||
}
|
||||
|
||||
# Invoke user-defined onStop callbacks, before the application's internal
|
||||
# onStop callbacks.
|
||||
on.exit({
|
||||
.globals$onStopCallbacks$invoke()
|
||||
.globals$onStopCallbacks <- Callbacks$new()
|
||||
}, add = TRUE)
|
||||
|
||||
# Extract appOptions (which is a list) and store them as shinyOptions, for
|
||||
# this app. (This is the only place we have to store settings that are
|
||||
# accessible both the UI and server portion of the app.)
|
||||
unconsumeAppOptions(appParts$appOptions)
|
||||
|
||||
# Set up the onEnd before we call onStart, so that it gets called even if an
|
||||
# Set up the onStop before we call onStart, so that it gets called even if an
|
||||
# error happens in onStart.
|
||||
if (!is.null(appParts$onEnd))
|
||||
on.exit(appParts$onEnd(), add = TRUE)
|
||||
if (!is.null(appParts$onStop))
|
||||
on.exit(appParts$onStop(), add = TRUE)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
|
||||
@@ -1022,3 +1045,9 @@ browserViewer <- function(browser = getOption("browser")) {
|
||||
inShinyServer <- function() {
|
||||
nzchar(Sys.getenv('SHINY_PORT'))
|
||||
}
|
||||
|
||||
# This check was moved out of the main function body because of an issue with
|
||||
# the RStudio debugger. (#1474)
|
||||
isEmptyMessage <- function(msg) {
|
||||
identical(charToRaw("\003\xe9"), msg)
|
||||
}
|
||||
|
||||
206
R/shiny.R
206
R/shiny.R
@@ -5,7 +5,7 @@ NULL
|
||||
#'
|
||||
#' Shiny makes it incredibly easy to build interactive web applications with R.
|
||||
#' Automatic "reactive" binding between inputs and outputs and extensive
|
||||
#' pre-built widgets make it possible to build beautiful, responsive, and
|
||||
#' prebuilt widgets make it possible to build beautiful, responsive, and
|
||||
#' powerful applications with minimal effort.
|
||||
#'
|
||||
#' The Shiny tutorial at \url{http://shiny.rstudio.com/tutorial/} explains
|
||||
@@ -142,6 +142,15 @@ toJSON <- function(x, ..., dataframe = "columns", null = "null", na = "null",
|
||||
keep_vec_names = keep_vec_names, json_verbatim = TRUE, ...)
|
||||
}
|
||||
|
||||
# If the input to jsonlite::fromJSON is not valid JSON, it will try to fetch a
|
||||
# URL or read a file from disk. We don't want to allow that.
|
||||
safeFromJSON <- function(txt, ...) {
|
||||
if (!jsonlite::validate(txt)) {
|
||||
stop("Argument 'txt' is not a valid JSON string.")
|
||||
}
|
||||
jsonlite::fromJSON(txt, ...)
|
||||
}
|
||||
|
||||
# Call the workerId func with no args to get the worker id, and with an arg to
|
||||
# set it.
|
||||
#
|
||||
@@ -639,6 +648,15 @@ ShinySession <- R6Class(
|
||||
values$input <- allInputs[items]
|
||||
}
|
||||
|
||||
# Apply preprocessor functions for inputs that have them.
|
||||
values$input <- lapply(
|
||||
setNames(names(values$input), names(values$input)),
|
||||
function(name) {
|
||||
preprocess <- private$getSnapshotPreprocessInput(name)
|
||||
preprocess(values$input[[name]])
|
||||
}
|
||||
)
|
||||
|
||||
values$input <- sortByName(values$input)
|
||||
}
|
||||
|
||||
@@ -658,6 +676,15 @@ ShinySession <- R6Class(
|
||||
}, logical(1))
|
||||
values$output <- values$output[!exclude_idx]
|
||||
|
||||
# Apply snapshotPreprocess functions for outputs that have them.
|
||||
values$output <- lapply(
|
||||
setNames(names(values$output), names(values$output)),
|
||||
function(name) {
|
||||
preprocess <- private$getSnapshotPreprocessOutput(name)
|
||||
preprocess(values$output[[name]])
|
||||
}
|
||||
)
|
||||
|
||||
values$output <- sortByName(values$output)
|
||||
}
|
||||
|
||||
@@ -712,6 +739,20 @@ ShinySession <- R6Class(
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
# Get the snapshotPreprocessOutput function for an output name. If no preprocess
|
||||
# function has been set, return the identity function.
|
||||
getSnapshotPreprocessOutput = function(name) {
|
||||
fun <- attr(private$.outputs[[name]], "snapshotPreprocess", exact = TRUE)
|
||||
fun %OR% identity
|
||||
},
|
||||
|
||||
# Get the snapshotPreprocessInput function for an input name. If no preprocess
|
||||
# function has been set, return the identity function.
|
||||
getSnapshotPreprocessInput = function(name) {
|
||||
fun <- private$.input$getMeta(name, "shiny.snapshot.preprocess")
|
||||
fun %OR% identity
|
||||
}
|
||||
),
|
||||
public = list(
|
||||
@@ -773,7 +814,7 @@ ShinySession <- R6Class(
|
||||
|
||||
if (!is.null(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)) {
|
||||
try({
|
||||
creds <- jsonlite::fromJSON(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)
|
||||
creds <- safeFromJSON(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)
|
||||
self$user <- creds$user
|
||||
self$groups <- creds$groups
|
||||
}, silent=FALSE)
|
||||
@@ -1192,6 +1233,13 @@ ShinySession <- R6Class(
|
||||
})
|
||||
|
||||
if (!hasPendingUpdates()) {
|
||||
# Normally, if there are no updates, simply return without sending
|
||||
# anything to the client. But if we are in test mode, we still want to
|
||||
# send a message with blank `values`, so that the client knows that
|
||||
# any changed inputs have been received by the server and processed.
|
||||
if (isTRUE(private$testMode)) {
|
||||
private$sendMessage( values = list() )
|
||||
}
|
||||
return(invisible())
|
||||
}
|
||||
|
||||
@@ -1453,6 +1501,37 @@ ShinySession <- R6Class(
|
||||
)
|
||||
)
|
||||
},
|
||||
sendInsertTab = function(inputId, liTag, divTag, menuName,
|
||||
target, position, select) {
|
||||
private$sendMessage(
|
||||
`shiny-insert-tab` = list(
|
||||
inputId = inputId,
|
||||
liTag = liTag,
|
||||
divTag = divTag,
|
||||
menuName = menuName,
|
||||
target = target,
|
||||
position = position,
|
||||
select = select
|
||||
)
|
||||
)
|
||||
},
|
||||
sendRemoveTab = function(inputId, target) {
|
||||
private$sendMessage(
|
||||
`shiny-remove-tab` = list(
|
||||
inputId = inputId,
|
||||
target = target
|
||||
)
|
||||
)
|
||||
},
|
||||
sendChangeTabVisibility = function(inputId, target, type) {
|
||||
private$sendMessage(
|
||||
`shiny-change-tab-visibility` = list(
|
||||
inputId = inputId,
|
||||
target = target,
|
||||
type = type
|
||||
)
|
||||
)
|
||||
},
|
||||
updateQueryString = function(queryString, mode) {
|
||||
private$sendMessage(updateQueryString = list(
|
||||
queryString = queryString, mode = mode))
|
||||
@@ -1491,7 +1570,8 @@ ShinySession <- R6Class(
|
||||
fileData <- private$fileUploadContext$getUploadOperation(jobId)$finish()
|
||||
private$.input$set(inputId, fileData)
|
||||
|
||||
private$.input$setMeta(inputId, "shiny.serializer", serializerFileInput)
|
||||
setSerializer(inputId, serializerFileInput)
|
||||
snapshotPreprocessInput(inputId, snapshotPreprocessorFileInput)
|
||||
|
||||
invisible()
|
||||
},
|
||||
@@ -1534,9 +1614,30 @@ ShinySession <- R6Class(
|
||||
}
|
||||
}
|
||||
|
||||
# @description Only applicable to files uploaded via IE. When possible,
|
||||
# adds the appropriate extension to temporary files created by
|
||||
# \code{mime::parse_multipart}.
|
||||
# @param multipart A named list as returned by
|
||||
# \code{mime::parse_multipart}
|
||||
# @return A named list with datapath updated to point to the new location
|
||||
# of the file, if an extension was added.
|
||||
maybeMoveIEUpload <- function(multipart) {
|
||||
if (is.null(multipart)) return(NULL)
|
||||
|
||||
lapply(multipart, function(input) {
|
||||
oldPath <- input$datapath
|
||||
newPath <- paste0(oldPath, maybeGetExtension(input$name))
|
||||
if (oldPath != newPath) {
|
||||
file.rename(oldPath, newPath)
|
||||
input$datapath <- newPath
|
||||
}
|
||||
input
|
||||
})
|
||||
}
|
||||
|
||||
if (matches[2] == 'uploadie' && identical(req$REQUEST_METHOD, "POST")) {
|
||||
id <- URLdecode(matches[3])
|
||||
res <- mime::parse_multipart(req)
|
||||
res <- maybeMoveIEUpload(mime::parse_multipart(req))
|
||||
private$.input$set(id, res[[id]])
|
||||
return(httpResponse(200, 'text/plain', 'OK'))
|
||||
}
|
||||
@@ -1861,17 +1962,6 @@ outputOptions <- function(x, name, ...) {
|
||||
.subset2(x, 'impl')$outputOptions(name, ...)
|
||||
}
|
||||
|
||||
|
||||
#' Mark an output to be excluded from test snapshots
|
||||
#'
|
||||
#' @param x A reactive which will be assigned to an output.
|
||||
#'
|
||||
#' @export
|
||||
snapshotExclude <- function(x) {
|
||||
markOutputAttrs(x, snapshotExclude = TRUE)
|
||||
}
|
||||
|
||||
|
||||
#' Add callbacks for Shiny session events
|
||||
#'
|
||||
#' These functions are for registering callbacks on Shiny session events.
|
||||
@@ -1903,6 +1993,9 @@ onFlushed <- function(fun, once = TRUE, session = getDefaultReactiveDomain()) {
|
||||
}
|
||||
|
||||
#' @rdname onFlush
|
||||
#'
|
||||
#' @seealso \code{\link{onStop}()} for registering callbacks that will be
|
||||
#' invoked when the application exits, or when a session ends.
|
||||
#' @export
|
||||
onSessionEnded <- function(fun, session = getDefaultReactiveDomain()) {
|
||||
session$onSessionEnded(fun)
|
||||
@@ -1927,3 +2020,86 @@ flushAllSessions <- function() {
|
||||
NULL
|
||||
})
|
||||
}
|
||||
|
||||
.globals$onStopCallbacks <- Callbacks$new()
|
||||
|
||||
#' Run code after an application or session ends
|
||||
#'
|
||||
#' This function registers callback functions that are invoked when the
|
||||
#' application exits (when \code{\link{runApp}} exits), or after each user
|
||||
#' session ends (when a client disconnects).
|
||||
#'
|
||||
#' @param fun A function that will be called after the app has finished running.
|
||||
#' @param session A scope for when the callback will run. If \code{onStop} is
|
||||
#' called from within the server function, this will default to the current
|
||||
#' session, and the callback will be invoked when the current session ends. If
|
||||
#' \code{onStop} is called outside a server function, then the callback will
|
||||
#' be invoked with the application exits.
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{onSessionEnded}()} for the same functionality, but at
|
||||
#' the session level only.
|
||||
#'
|
||||
#' @return A function which, if invoked, will cancel the callback.
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # Open this application in multiple browsers, then close the browsers.
|
||||
#' shinyApp(
|
||||
#' ui = basicPage("onStop demo"),
|
||||
#'
|
||||
#' server = function(input, output, session) {
|
||||
#' onStop(function() cat("Session stopped\n"))
|
||||
#' },
|
||||
#'
|
||||
#' onStart = function() {
|
||||
#' cat("Doing application setup\n")
|
||||
#'
|
||||
#' onStop(function() {
|
||||
#' cat("Doing application cleanup\n")
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#' # In the example above, onStop() is called inside of onStart(). This is
|
||||
#' # the pattern that should be used when creating a shinyApp() object from
|
||||
#' # a function, or at the console. If instead you are writing an app.R which
|
||||
#' # will be invoked with runApp(), you can do it that way, or put the onStop()
|
||||
#' # before the shinyApp() call, as shown below.
|
||||
#'
|
||||
#' \dontrun{
|
||||
#' # ==== app.R ====
|
||||
#' cat("Doing application setup\n")
|
||||
#' onStop(function() {
|
||||
#' cat("Doing application cleanup\n")
|
||||
#' })
|
||||
#'
|
||||
#' shinyApp(
|
||||
#' ui = basicPage("onStop demo"),
|
||||
#'
|
||||
#' server = function(input, output, session) {
|
||||
#' onStop(function() cat("Session stopped\n"))
|
||||
#' }
|
||||
#' )
|
||||
#' # ==== end app.R ====
|
||||
#'
|
||||
#'
|
||||
#' # Similarly, if you have a global.R, you can call onStop() from there.
|
||||
#' # ==== global.R ====
|
||||
#' cat("Doing application setup\n")
|
||||
#' onStop(function() {
|
||||
#' cat("Doing application cleanup\n")
|
||||
#' })
|
||||
#' # ==== end global.R ====
|
||||
#' }
|
||||
#' @export
|
||||
onStop <- function(fun, session = getDefaultReactiveDomain()) {
|
||||
if (is.null(getDefaultReactiveDomain())) {
|
||||
return(.globals$onStopCallbacks$register(fun))
|
||||
} else {
|
||||
# Note: In the future if we allow scoping the onStop() callback to modules
|
||||
# and allow modules to be stopped, then session_proxy objects will need
|
||||
# its own implementation of $onSessionEnded.
|
||||
return(session$onSessionEnded(fun))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,9 +93,13 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
#'
|
||||
#' @inheritParams markRenderFunction
|
||||
#' @param snapshotExclude If TRUE, exclude the output from test snapshots.
|
||||
#' @param snapshotPreprocess A function for preprocessing the value before
|
||||
#' taking a test snapshot.
|
||||
#'
|
||||
#' @keywords internal
|
||||
markOutputAttrs <- function(renderFunc, snapshotExclude = NULL) {
|
||||
markOutputAttrs <- function(renderFunc, snapshotExclude = NULL,
|
||||
snapshotPreprocess = NULL)
|
||||
{
|
||||
# Add the outputAttrs attribute if necessary
|
||||
if (is.null(attr(renderFunc, "outputAttrs", TRUE))) {
|
||||
attr(renderFunc, "outputAttrs") <- list()
|
||||
@@ -105,6 +109,10 @@ markOutputAttrs <- function(renderFunc, snapshotExclude = NULL) {
|
||||
attr(renderFunc, "outputAttrs")$snapshotExclude <- snapshotExclude
|
||||
}
|
||||
|
||||
if (!is.null(snapshotPreprocess)) {
|
||||
attr(renderFunc, "outputAttrs")$snapshotPreprocess <- snapshotPreprocess
|
||||
}
|
||||
|
||||
renderFunc
|
||||
}
|
||||
|
||||
@@ -533,7 +541,18 @@ renderDataTable <- function(expr, options = NULL, searchDelay = 500,
|
||||
)
|
||||
}
|
||||
|
||||
markRenderFunction(dataTableOutput, renderFunc, outputArgs = outputArgs)
|
||||
renderFunc <- markRenderFunction(dataTableOutput, renderFunc, outputArgs = outputArgs)
|
||||
|
||||
renderFunc <- snapshotPreprocessOutput(renderFunc, function(value) {
|
||||
# Remove the action field so that it's not saved in test snapshots. It
|
||||
# contains a value that changes every time an app is run, and shouldn't be
|
||||
# stored for test snapshots. It will be something like:
|
||||
# "session/e0d14d3fe97f672f9655a127f2a1e079/dataobj/table?w=&nonce=7f5d6d54e22450a3"
|
||||
value$action <- NULL
|
||||
value
|
||||
})
|
||||
|
||||
renderFunc
|
||||
}
|
||||
|
||||
# a data frame containing the DataTables 1.9 and 1.10 names
|
||||
|
||||
44
R/snapshot.R
Normal file
44
R/snapshot.R
Normal file
@@ -0,0 +1,44 @@
|
||||
#' Mark an output to be excluded from test snapshots
|
||||
#'
|
||||
#' @param x A reactive which will be assigned to an output.
|
||||
#'
|
||||
#' @export
|
||||
snapshotExclude <- function(x) {
|
||||
markOutputAttrs(x, snapshotExclude = TRUE)
|
||||
}
|
||||
|
||||
#' Add a function for preprocessing an output before taking a test snapshot
|
||||
#'
|
||||
#' @param x A reactive which will be assigned to an output.
|
||||
#' @param fun A function that takes the output value as an input and returns a
|
||||
#' modified value. The returned value will be used for the test snapshot.
|
||||
#'
|
||||
#' @export
|
||||
snapshotPreprocessOutput <- function(x, fun) {
|
||||
markOutputAttrs(x, snapshotPreprocess = fun)
|
||||
}
|
||||
|
||||
|
||||
#' Add a function for preprocessing an input before taking a test snapshot
|
||||
#'
|
||||
#' @param inputId Name of the input value.
|
||||
#' @param fun A function that takes the input value and returns a modified
|
||||
#' value. The returned value will be used for the test snapshot.
|
||||
#' @param session A Shiny session object.
|
||||
#'
|
||||
#' @export
|
||||
snapshotPreprocessInput <- function(inputId, fun, session = getDefaultReactiveDomain()) {
|
||||
if (is.null(session)) {
|
||||
stop("snapshotPreprocessInput() needs a session object.")
|
||||
}
|
||||
|
||||
input_impl <- .subset2(session$input, "impl")
|
||||
input_impl$setMeta(inputId, "shiny.snapshot.preprocess", fun)
|
||||
}
|
||||
|
||||
|
||||
# Strip out file path from fileInput value
|
||||
snapshotPreprocessorFileInput <- function(value) {
|
||||
value$datapath <- basename(value$datapath)
|
||||
value
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#' @param placeholder The placeholder to set for the input object.
|
||||
#'
|
||||
#' @seealso \code{\link{textInput}}
|
||||
#'
|
||||
@@ -34,15 +35,15 @@
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
|
||||
message <- dropNulls(list(label=label, value=value))
|
||||
updateTextInput <- function(session, inputId, label = NULL, value = NULL, placeholder = NULL) {
|
||||
message <- dropNulls(list(label=label, value=value, placeholder=placeholder))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' Change the value of a textarea input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#' @inheritParams updateTextInput
|
||||
#'
|
||||
#' @seealso \code{\link{textAreaInput}}
|
||||
#'
|
||||
@@ -106,7 +107,10 @@ updateTextAreaInput <- updateTextInput
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxInput <- updateTextInput
|
||||
updateCheckboxInput <- function(session, inputId, label = NULL, value = NULL) {
|
||||
message <- dropNulls(list(label=label, value=value))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' Change the label or icon of an action button on the client
|
||||
@@ -651,7 +655,7 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selectizeJSON <- function(data, req) {
|
||||
query <- parseQueryString(req$QUERY_STRING)
|
||||
# extract the query variables, conjunction (and/or), search string, maximum options
|
||||
var <- c(jsonlite::fromJSON(query$field))
|
||||
var <- c(safeFromJSON(query$field))
|
||||
cjn <- if (query$conju == 'and') all else any
|
||||
# all keywords in lower-case, for case-insensitive matching
|
||||
key <- unique(strsplit(tolower(query$query), '\\s+')[[1]])
|
||||
|
||||
84
R/utils.R
84
R/utils.R
@@ -43,53 +43,43 @@ repeatable <- function(rngfunc, seed = stats::runif(1, 0, .Machine$integer.max))
|
||||
}
|
||||
}
|
||||
|
||||
# Temporarily set x in env to value, evaluate expr, and
|
||||
# then restore x to its original state
|
||||
withTemporary <- function(env, x, value, expr, unset = FALSE) {
|
||||
|
||||
if (exists(x, envir = env, inherits = FALSE)) {
|
||||
oldValue <- get(x, envir = env, inherits = FALSE)
|
||||
on.exit(
|
||||
assign(x, oldValue, envir = env, inherits = FALSE),
|
||||
add = TRUE)
|
||||
} else {
|
||||
on.exit(
|
||||
rm(list = x, envir = env, inherits = FALSE),
|
||||
add = TRUE
|
||||
)
|
||||
}
|
||||
|
||||
if (!missing(value) && !isTRUE(unset))
|
||||
assign(x, value, envir = env, inherits = FALSE)
|
||||
else {
|
||||
if (exists(x, envir = env, inherits = FALSE))
|
||||
rm(list = x, envir = env, inherits = FALSE)
|
||||
}
|
||||
force(expr)
|
||||
}
|
||||
|
||||
.globals$ownSeed <- NULL
|
||||
# Evaluate an expression using Shiny's own private stream of
|
||||
# randomness (not affected by set.seed).
|
||||
withPrivateSeed <- function(expr) {
|
||||
withTemporary(.GlobalEnv, ".Random.seed",
|
||||
.globals$ownSeed, unset=is.null(.globals$ownSeed), {
|
||||
tryCatch({
|
||||
expr
|
||||
}, finally = {
|
||||
.globals$ownSeed <- getExists('.Random.seed', 'numeric', globalenv())
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
# Save the old seed if present.
|
||||
if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
|
||||
hasOrigSeed <- TRUE
|
||||
origSeed <- .GlobalEnv$.Random.seed
|
||||
} else {
|
||||
hasOrigSeed <- FALSE
|
||||
}
|
||||
|
||||
# a homemade version of set.seed(NULL) for backward compatibility with R 2.15.x
|
||||
reinitializeSeed <- if (getRversion() >= '3.0.0') {
|
||||
function() set.seed(NULL)
|
||||
} else function() {
|
||||
if (exists('.Random.seed', globalenv()))
|
||||
rm(list = '.Random.seed', pos = globalenv())
|
||||
stats::runif(1) # generate any random numbers so R can reinitialize the seed
|
||||
# Swap in the private seed.
|
||||
if (is.null(.globals$ownSeed)) {
|
||||
if (hasOrigSeed) {
|
||||
# Move old seed out of the way if present.
|
||||
rm(.Random.seed, envir = .GlobalEnv, inherits = FALSE)
|
||||
}
|
||||
} else {
|
||||
.GlobalEnv$.Random.seed <- .globals$ownSeed
|
||||
}
|
||||
|
||||
# On exit, save the modified private seed, and put the old seed back.
|
||||
on.exit({
|
||||
.globals$ownSeed <- .GlobalEnv$.Random.seed
|
||||
|
||||
if (hasOrigSeed) {
|
||||
.GlobalEnv$.Random.seed <- origSeed
|
||||
} else {
|
||||
rm(.Random.seed, envir = .GlobalEnv, inherits = FALSE)
|
||||
}
|
||||
# Need to call this to make sure that the value of .Random.seed gets put
|
||||
# into R's internal RNG state. (Issue #1763)
|
||||
httpuv::getRNGState()
|
||||
})
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
# Version of runif that runs with private seed
|
||||
@@ -225,7 +215,7 @@ sortByName <- function(x) {
|
||||
# R >=3.2.0, this wrapper is not necessary.
|
||||
list2env2 <- function(x, ...) {
|
||||
# Ensure that zero-length lists have a name attribute
|
||||
if (length(x) == 0)
|
||||
if (length(x) == 0)
|
||||
attr(x, "names") <- character(0)
|
||||
|
||||
list2env(x, ...)
|
||||
@@ -672,6 +662,9 @@ Callbacks <- R6Class(
|
||||
.callbacks <<- Map$new()
|
||||
},
|
||||
register = function(callback) {
|
||||
if (!is.function(callback)) {
|
||||
stop("callback must be a function")
|
||||
}
|
||||
id <- as.character(.nextId)
|
||||
.nextId <<- .nextId - 1L
|
||||
.callbacks$set(id, callback)
|
||||
@@ -1539,7 +1532,10 @@ writeUTF8 <- function(text, ...) {
|
||||
writeLines(text, ..., useBytes = TRUE)
|
||||
}
|
||||
|
||||
URLdecode <- decodeURIComponent
|
||||
URLdecode <- function(value) {
|
||||
decodeURIComponent(value)
|
||||
}
|
||||
|
||||
URLencode <- function(value, reserved = FALSE) {
|
||||
value <- enc2utf8(value)
|
||||
if (reserved) encodeURIComponent(value) else encodeURI(value)
|
||||
|
||||
21
README.md
21
README.md
@@ -16,7 +16,7 @@ For an introduction and examples, visit the [Shiny Dev Center](http://shiny.rstu
|
||||
* Works in any R environment (Console R, Rgui for Windows or Mac, ESS, StatET, RStudio, etc.).
|
||||
* Attractive default UI theme based on [Bootstrap](http://getbootstrap.com/).
|
||||
* A highly customizable slider widget with built-in support for animation.
|
||||
* Pre-built output widgets for displaying plots, tables, and printed output of R objects.
|
||||
* Prebuilt output widgets for displaying plots, tables, and printed output of R objects.
|
||||
* Fast bidirectional communication between the web browser and R using the [httpuv](https://github.com/rstudio/httpuv) package.
|
||||
* Uses a [reactive](http://en.wikipedia.org/wiki/Reactive_programming) programming model that eliminates messy event handling code, so you can focus on the code that really matters.
|
||||
* Develop and redistribute your own Shiny widgets that other developers can easily drop into their own applications (coming soon!).
|
||||
@@ -43,22 +43,19 @@ To learn more we highly recommend you check out the [Shiny Tutorial](http://shin
|
||||
|
||||
We hope you enjoy using Shiny. If you have general questions about using Shiny, please use the Shiny [mailing list](https://groups.google.com/forum/#!forum/shiny-discuss). For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues).
|
||||
|
||||
## Bootstrap 3 migration
|
||||
|
||||
Shiny versions 0.10.2.2 and below used the Bootstrap 2 web framework. After 0.10.2.2, Shiny switched to Bootstrap 3. For most users, the upgrade should be seamless. However, if you have have customized your HTML-generating code to use features specific to Bootstrap 2, you may need to update your code to work with Bootstrap 3.
|
||||
|
||||
If you do not wish to update your code at this time, you can use the [shinybootstrap2](https://github.com/rstudio/shinybootstrap2) package for backward compatibility.
|
||||
|
||||
If you prefer to install an older version of Shiny, you can do it using the devtools package:
|
||||
|
||||
```R
|
||||
devtools::install_version("shiny", version = "0.10.2.2")
|
||||
```
|
||||
|
||||
## Development notes
|
||||
|
||||
The Javascript code in Shiny is minified using tools that run on Node.js. See the tools/ directory for more information.
|
||||
|
||||
A set of application-level tests reside in the [shiny-test-apps](https://github.com/rstudio/shiny-test-apps) repository, which is set up as a git submodule of this repository. To use the full-application tests, simply update the submodule:
|
||||
|
||||
```
|
||||
git submodule update --init
|
||||
```
|
||||
|
||||
This will clone the shiny-test-apps repository into the directory tests/testthat/apps. When you run tests for shiny, it will also test the applications.
|
||||
|
||||
## Guidelines for contributing
|
||||
|
||||
We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.md](CONTRIBUTING.md) file for detailed guidelines of how to contribute.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
This small Shiny application demonstrates Shiny's automatic UI updates. Move
|
||||
the *Number of bins* slider and notice how the `renderPlot` expression is
|
||||
automatically re-evaluated when its dependant, `input$bins`, changes,
|
||||
causing a histogram with a new number of bins to be rendered.
|
||||
This small Shiny application demonstrates Shiny's automatic UI updates.
|
||||
|
||||
Move the *Number of bins* slider and notice how the `renderPlot` expression is automatically re-evaluated when its dependant, `input$bins`, changes, causing a histogram with a new number of bins to be rendered.
|
||||
|
||||
59
inst/examples/01_hello/app.R
Normal file
59
inst/examples/01_hello/app.R
Normal file
@@ -0,0 +1,59 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for app that draws a histogram ----
|
||||
ui <- fluidPage(
|
||||
|
||||
# App title ----
|
||||
titlePanel("Hello Shiny!"),
|
||||
|
||||
# Sidebar layout with input and output definitions ----
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar panel for inputs ----
|
||||
sidebarPanel(
|
||||
|
||||
# Input: Slider for the number of bins ----
|
||||
sliderInput(inputId = "bins",
|
||||
label = "Number of bins:",
|
||||
min = 1,
|
||||
max = 50,
|
||||
value = 30)
|
||||
|
||||
),
|
||||
|
||||
# Main panel for displaying outputs ----
|
||||
mainPanel(
|
||||
|
||||
# Output: Histogram ----
|
||||
plotOutput(outputId = "distPlot")
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic required to draw a histogram ----
|
||||
server <- function(input, output) {
|
||||
|
||||
# Histogram of the Old Faithful Geyser Data ----
|
||||
# with requested number of bins
|
||||
# This expression that generates a histogram is wrapped in a call
|
||||
# to renderPlot to indicate that:
|
||||
#
|
||||
# 1. It is "reactive" and therefore should be automatically
|
||||
# re-executed when inputs (input$bins) change
|
||||
# 2. Its output type is a plot
|
||||
output$distPlot <- renderPlot({
|
||||
|
||||
x <- faithful$waiting
|
||||
bins <- seq(min(x), max(x), length.out = input$bins + 1)
|
||||
|
||||
hist(x, breaks = bins, col = "#75AADB", border = "white",
|
||||
xlab = "Waiting time to next eruption (in mins)",
|
||||
main = "Histogram of waiting times")
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui = ui, server = server)
|
||||
@@ -1,21 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic required to draw a histogram
|
||||
function(input, output) {
|
||||
|
||||
# Expression that generates a histogram. 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 <- renderPlot({
|
||||
x <- faithful[, 2] # Old Faithful Geyser data
|
||||
bins <- seq(min(x), max(x), length.out = input$bins + 1)
|
||||
|
||||
# draw the histogram with the specified number of bins
|
||||
hist(x, breaks = bins, col = 'darkgray', border = 'white')
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for application that draws a histogram
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Hello Shiny!"),
|
||||
|
||||
# Sidebar with a slider input for the number of bins
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
sliderInput("bins",
|
||||
"Number of bins:",
|
||||
min = 1,
|
||||
max = 50,
|
||||
value = 30)
|
||||
),
|
||||
|
||||
# Show a plot of the generated distribution
|
||||
mainPanel(
|
||||
plotOutput("distPlot")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1 +1 @@
|
||||
This example demonstrates output of raw text from R using the `renderPrint` function in `server.R` and the `verbatimTextOutput` function in `ui.R`. In this case, a textual summary of the data is shown using R's built-in `summary` function.
|
||||
This example demonstrates output of raw text from R using the `renderPrint` function in `server` and the `verbatimTextOutput` function in `ui`. In this case, a textual summary of the data is shown using R's built-in `summary` function.
|
||||
|
||||
64
inst/examples/02_text/app.R
Normal file
64
inst/examples/02_text/app.R
Normal file
@@ -0,0 +1,64 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer app ----
|
||||
ui <- fluidPage(
|
||||
|
||||
# App title ----
|
||||
titlePanel("Shiny Text"),
|
||||
|
||||
# Sidebar layout with a input and output definitions ----
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar panel for inputs ----
|
||||
sidebarPanel(
|
||||
|
||||
# Input: Selector for choosing dataset ----
|
||||
selectInput(inputId = "dataset",
|
||||
label = "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
# Input: Numeric entry for number of obs to view ----
|
||||
numericInput(inputId = "obs",
|
||||
label = "Number of observations to view:",
|
||||
value = 10)
|
||||
),
|
||||
|
||||
# Main panel for displaying outputs ----
|
||||
mainPanel(
|
||||
|
||||
# Output: Verbatim text for data summary ----
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
# Output: HTML table with requested number of observations ----
|
||||
tableOutput("view")
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic to summarize and view selected dataset ----
|
||||
server <- function(input, output) {
|
||||
|
||||
# Return the requested dataset ----
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# Generate a summary of the dataset ----
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations ----
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui = ui, server = server)
|
||||
@@ -1,26 +0,0 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the selected
|
||||
# dataset
|
||||
function(input, output) {
|
||||
|
||||
# Return the requested dataset
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# Generate a summary of the dataset
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Shiny Text"),
|
||||
|
||||
# Sidebar with controls to select a dataset and specify the
|
||||
# number of observations to view
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
numericInput("obs", "Number of observations to view:", 10)
|
||||
),
|
||||
|
||||
# Show a summary of the dataset and an HTML table with the
|
||||
# requested number of observations
|
||||
mainPanel(
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
This example demonstrates a core feature of Shiny: **reactivity**. In `server.R`, a reactive called `datasetInput` is declared.
|
||||
This example demonstrates a core feature of Shiny: **reactivity**. In the `server` function, a reactive called `datasetInput` is declared.
|
||||
|
||||
Notice that the reactive expression depends on the input expression `input$dataset`, and that it's used by both the output expression `output$summary` and `output$view`. Try changing the dataset (using *Choose a dataset*) while looking at the reactive and then at the outputs; you will see first the reactive and then its dependencies flash.
|
||||
Notice that the reactive expression depends on the input expression `input$dataset`, and that it's used by two output expressions: `output$summary` and `output$view`. Try changing the dataset (using *Choose a dataset*) while looking at the reactive and then at the outputs; you will see first the reactive and then its dependencies flash.
|
||||
|
||||
Notice also that the reactive expression doesn't just update whenever anything changes--only the inputs it depends on will trigger an update. Change the "Caption" field and notice how only the `output$caption` expression is re-evaluated; the reactive and its dependents are left alone.
|
||||
|
||||
102
inst/examples/03_reactivity/app.R
Normal file
102
inst/examples/03_reactivity/app.R
Normal file
@@ -0,0 +1,102 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer app ----
|
||||
ui <- fluidPage(
|
||||
|
||||
# App title ----
|
||||
titlePanel("Reactivity"),
|
||||
|
||||
# Sidebar layout with input and output definitions ----
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar panel for inputs ----
|
||||
sidebarPanel(
|
||||
|
||||
# Input: Text for providing a caption ----
|
||||
# Note: Changes made to the caption in the textInput control
|
||||
# are updated in the output area immediately as you type
|
||||
textInput(inputId = "caption",
|
||||
label = "Caption:",
|
||||
value = "Data Summary"),
|
||||
|
||||
# Input: Selector for choosing dataset ----
|
||||
selectInput(inputId = "dataset",
|
||||
label = "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
# Input: Numeric entry for number of obs to view ----
|
||||
numericInput(inputId = "obs",
|
||||
label = "Number of observations to view:",
|
||||
value = 10)
|
||||
|
||||
),
|
||||
|
||||
# Main panel for displaying outputs ----
|
||||
mainPanel(
|
||||
|
||||
# Output: Formatted text for caption ----
|
||||
h3(textOutput("caption", container = span)),
|
||||
|
||||
# Output: Verbatim text for data summary ----
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
# Output: HTML table with requested number of observations ----
|
||||
tableOutput("view")
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic to summarize and view selected dataset ----
|
||||
server <- function(input, output) {
|
||||
|
||||
# Return the requested dataset ----
|
||||
# By declaring datasetInput 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,
|
||||
# i.e. it only executes a single time
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# Create caption ----
|
||||
# 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. New caption is pushed back to the browser for re-display
|
||||
#
|
||||
# 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 <- renderText({
|
||||
input$caption
|
||||
})
|
||||
|
||||
# Generate a summary of the dataset ----
|
||||
# The output$summary depends on the datasetInput reactive
|
||||
# expression, so will be re-executed whenever datasetInput is
|
||||
# invalidated, i.e. whenever the input$dataset changes
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations ----
|
||||
# The output$view depends on both the databaseInput reactive
|
||||
# expression and input$obs, so it will be re-executed whenever
|
||||
# input$dataset or input$obs is changed
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui, server)
|
||||
@@ -1,53 +0,0 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the selected
|
||||
# dataset
|
||||
function(input, output) {
|
||||
|
||||
# By declaring datasetInput 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)
|
||||
#
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# 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 expressions
|
||||
# below don't depend on input$caption, those expressions are
|
||||
# NOT called when input$caption changes.
|
||||
output$caption <- renderText({
|
||||
input$caption
|
||||
})
|
||||
|
||||
# The output$summary depends on the datasetInput reactive
|
||||
# expression, so will be re-executed whenever datasetInput is
|
||||
# invalidated
|
||||
# (i.e. whenever the input$dataset changes)
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# 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 <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Reactivity"),
|
||||
|
||||
# Sidebar with controls to provide a caption, select a dataset,
|
||||
# and specify the number of observations to view. Note that
|
||||
# changes made to the caption in the textInput control are
|
||||
# updated in the output area immediately as you type
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
textInput("caption", "Caption:", "Data Summary"),
|
||||
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
numericInput("obs", "Number of observations to view:", 10)
|
||||
),
|
||||
|
||||
|
||||
# Show the caption, a summary of the dataset and an HTML
|
||||
# table with the requested number of observations
|
||||
mainPanel(
|
||||
h3(textOutput("caption", container = span)),
|
||||
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
This example demonstrates the following concepts:
|
||||
|
||||
* **Global variables**: The `mpgData` variable is declared outside the `shinyServer` function. This makes it available anywhere inside `shinyServer`. The code in `server.R` outside `shinyServer` is only run once when the app starts up, so it can't contain user input.
|
||||
* **Reactive expressions**: `formulaText` is a reactive expression. Note how it re-evaluates when the Variable field is changed, but not when the Show Outliers box is ticked.
|
||||
- **Global variables**: The `mpgData` variable is declared outside of the `ui` and `server` function definitions. This makes it available anywhere inside `app.R`. The code in `app.R` outside of `ui` and `server` function definitions is only run once when the app starts up, so it can't contain user input.
|
||||
- **Reactive expressions**: `formulaText` is a reactive expression. Note how it re-evaluates when the Variable field is changed, but not when the Show Outliers box is unchecked.
|
||||
|
||||
75
inst/examples/04_mpg/app.R
Normal file
75
inst/examples/04_mpg/app.R
Normal file
@@ -0,0 +1,75 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# Data pre-processing ----
|
||||
# Tweak the "am" variable to have nicer factor labels -- since this
|
||||
# doesn't rely on any user inputs, we can do this once at startup
|
||||
# and then use the value throughout the lifetime of the app
|
||||
mpgData <- mtcars
|
||||
mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
|
||||
|
||||
|
||||
# Define UI for miles per gallon app ----
|
||||
ui <- fluidPage(
|
||||
|
||||
# App title ----
|
||||
titlePanel("Miles Per Gallon"),
|
||||
|
||||
# Sidebar layout with input and output definitions ----
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar panel for inputs ----
|
||||
sidebarPanel(
|
||||
|
||||
# Input: Selector for variable to plot against mpg ----
|
||||
selectInput("variable", "Variable:",
|
||||
c("Cylinders" = "cyl",
|
||||
"Transmission" = "am",
|
||||
"Gears" = "gear")),
|
||||
|
||||
# Input: Checkbox for whether outliers should be included ----
|
||||
checkboxInput("outliers", "Show outliers", TRUE)
|
||||
|
||||
),
|
||||
|
||||
# Main panel for displaying outputs ----
|
||||
mainPanel(
|
||||
|
||||
# Output: Formatted text for caption ----
|
||||
h3(textOutput("caption")),
|
||||
|
||||
# Output: Plot of the requested variable against mpg ----
|
||||
plotOutput("mpgPlot")
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic to plot various variables against mpg ----
|
||||
server <- function(input, output) {
|
||||
|
||||
# Compute the formula text ----
|
||||
# This is in a reactive expression since it is shared by the
|
||||
# output$caption and output$mpgPlot functions
|
||||
formulaText <- reactive({
|
||||
paste("mpg ~", input$variable)
|
||||
})
|
||||
|
||||
# Return the formula text for printing as a caption ----
|
||||
output$caption <- renderText({
|
||||
formulaText()
|
||||
})
|
||||
|
||||
# Generate a plot of the requested variable against mpg ----
|
||||
# and only exclude outliers if requested
|
||||
output$mpgPlot <- renderPlot({
|
||||
boxplot(as.formula(formulaText()),
|
||||
data = mpgData,
|
||||
outline = input$outliers,
|
||||
col = "#75AADB", pch = 19)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui, server)
|
||||
@@ -1,34 +0,0 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# We tweak the "am" field to have nicer factor labels. Since
|
||||
# this doesn't rely on any user inputs we can do this once at
|
||||
# startup and then use the value throughout the lifetime of the
|
||||
# application
|
||||
mpgData <- mtcars
|
||||
mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
|
||||
|
||||
|
||||
# Define server logic required to plot various variables against
|
||||
# mpg
|
||||
function(input, output) {
|
||||
|
||||
# Compute the formula text in a reactive expression since it is
|
||||
# shared by the output$caption and output$mpgPlot functions
|
||||
formulaText <- reactive({
|
||||
paste("mpg ~", input$variable)
|
||||
})
|
||||
|
||||
# Return the formula text for printing as a caption
|
||||
output$caption <- renderText({
|
||||
formulaText()
|
||||
})
|
||||
|
||||
# Generate a plot of the requested variable against mpg and
|
||||
# only include outliers if requested
|
||||
output$mpgPlot <- renderPlot({
|
||||
boxplot(as.formula(formulaText()),
|
||||
data = mpgData,
|
||||
outline = input$outliers)
|
||||
})
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for miles per gallon application
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Miles Per Gallon"),
|
||||
|
||||
# Sidebar with controls to select the variable to plot against
|
||||
# mpg and to specify whether outliers should be included
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("variable", "Variable:",
|
||||
c("Cylinders" = "cyl",
|
||||
"Transmission" = "am",
|
||||
"Gears" = "gear")),
|
||||
|
||||
checkboxInput("outliers", "Show outliers", FALSE)
|
||||
),
|
||||
|
||||
# Show the caption and plot of the requested variable against
|
||||
# mpg
|
||||
mainPanel(
|
||||
h3(textOutput("caption")),
|
||||
|
||||
plotOutput("mpgPlot")
|
||||
)
|
||||
)
|
||||
)
|
||||
86
inst/examples/05_sliders/app.R
Normal file
86
inst/examples/05_sliders/app.R
Normal file
@@ -0,0 +1,86 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for slider demo app ----
|
||||
ui <- fluidPage(
|
||||
|
||||
# App title ----
|
||||
titlePanel("Sliders"),
|
||||
|
||||
# Sidebar layout with input and output definitions ----
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar to demonstrate various slider options ----
|
||||
sidebarPanel(
|
||||
|
||||
# Input: Simple integer interval ----
|
||||
sliderInput("integer", "Integer:",
|
||||
min = 0, max = 1000,
|
||||
value = 500),
|
||||
|
||||
# Input: Decimal interval with step value ----
|
||||
sliderInput("decimal", "Decimal:",
|
||||
min = 0, max = 1,
|
||||
value = 0.5, step = 0.1),
|
||||
|
||||
# Input: Specification of range within an interval ----
|
||||
sliderInput("range", "Range:",
|
||||
min = 1, max = 1000,
|
||||
value = c(200,500)),
|
||||
|
||||
# Input: Custom currency format for with basic animation ----
|
||||
sliderInput("format", "Custom Format:",
|
||||
min = 0, max = 10000,
|
||||
value = 0, step = 2500,
|
||||
pre = "$", sep = ",",
|
||||
animate = TRUE),
|
||||
|
||||
# Input: Animation with custom interval (in ms) ----
|
||||
# to control speed, plus looping
|
||||
sliderInput("animation", "Looping Animation:",
|
||||
min = 1, max = 2000,
|
||||
value = 1, step = 10,
|
||||
animate =
|
||||
animationOptions(interval = 300, loop = TRUE))
|
||||
|
||||
),
|
||||
|
||||
# Main panel for displaying outputs ----
|
||||
mainPanel(
|
||||
|
||||
# Output: Table summarizing the values entered ----
|
||||
tableOutput("values")
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic for slider examples ----
|
||||
server <- function(input, output) {
|
||||
|
||||
# Reactive expression to create data frame of all input values ----
|
||||
sliderValues <- reactive({
|
||||
|
||||
data.frame(
|
||||
Name = c("Integer",
|
||||
"Decimal",
|
||||
"Range",
|
||||
"Custom Format",
|
||||
"Animation"),
|
||||
Value = as.character(c(input$integer,
|
||||
input$decimal,
|
||||
paste(input$range, collapse = " "),
|
||||
input$format,
|
||||
input$animation)),
|
||||
stringsAsFactors = FALSE)
|
||||
|
||||
})
|
||||
|
||||
# Show the values in an HTML table ----
|
||||
output$values <- renderTable({
|
||||
sliderValues()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui, server)
|
||||
@@ -1,29 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for slider examples
|
||||
function(input, output) {
|
||||
|
||||
# Reactive expression to compose a data frame containing all of
|
||||
# the values
|
||||
sliderValues <- reactive({
|
||||
|
||||
# Compose data frame
|
||||
data.frame(
|
||||
Name = c("Integer",
|
||||
"Decimal",
|
||||
"Range",
|
||||
"Custom Format",
|
||||
"Animation"),
|
||||
Value = as.character(c(input$integer,
|
||||
input$decimal,
|
||||
paste(input$range, collapse=' '),
|
||||
input$format,
|
||||
input$animation)),
|
||||
stringsAsFactors=FALSE)
|
||||
})
|
||||
|
||||
# Show the values using an HTML table
|
||||
output$values <- renderTable({
|
||||
sliderValues()
|
||||
})
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for slider demo application
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Sliders"),
|
||||
|
||||
# Sidebar with sliders that demonstrate various available
|
||||
# options
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
# Simple integer interval
|
||||
sliderInput("integer", "Integer:",
|
||||
min=0, max=1000, value=500),
|
||||
|
||||
# Decimal interval with step value
|
||||
sliderInput("decimal", "Decimal:",
|
||||
min = 0, max = 1, value = 0.5, step= 0.1),
|
||||
|
||||
# Specification of range within an interval
|
||||
sliderInput("range", "Range:",
|
||||
min = 1, max = 1000, value = c(200,500)),
|
||||
|
||||
# Provide a custom currency format for value display,
|
||||
# with basic animation
|
||||
sliderInput("format", "Custom Format:",
|
||||
min = 0, max = 10000, value = 0, step = 2500,
|
||||
pre = "$", sep = ",", animate=TRUE),
|
||||
|
||||
# Animation with custom interval (in ms) to control speed,
|
||||
# plus looping
|
||||
sliderInput("animation", "Looping Animation:", 1, 2000, 1,
|
||||
step = 10, animate =
|
||||
animationOptions(interval=300, loop=TRUE))
|
||||
),
|
||||
|
||||
# Show a table summarizing the values entered
|
||||
mainPanel(
|
||||
tableOutput("values")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -2,7 +2,7 @@ This example demonstrates the `tabsetPanel` and `tabPanel` widgets.
|
||||
|
||||
Notice that outputs that are not visible are not re-evaluated until they become visible. Try this:
|
||||
|
||||
1. Scroll to the bottom of `server.R`
|
||||
1. Scroll to the bottom of the `server` function. You might need to use the *show with app* option so you can easily view the code and interact with the app at the same time.
|
||||
2. Change the number of observations, and observe that only `output$plot` is evaluated.
|
||||
3. Click the Summary tab, and observe that `output$summary` is evaluated.
|
||||
4. Change the number of observations again, and observe that now only `output$summary` is evaluated.
|
||||
|
||||
92
inst/examples/06_tabsets/app.R
Normal file
92
inst/examples/06_tabsets/app.R
Normal file
@@ -0,0 +1,92 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for random distribution app ----
|
||||
ui <- fluidPage(
|
||||
|
||||
# App title ----
|
||||
titlePanel("Tabsets"),
|
||||
|
||||
# Sidebar layout with input and output definitions ----
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar panel for inputs ----
|
||||
sidebarPanel(
|
||||
|
||||
# Input: Select the random distribution type ----
|
||||
radioButtons("dist", "Distribution type:",
|
||||
c("Normal" = "norm",
|
||||
"Uniform" = "unif",
|
||||
"Log-normal" = "lnorm",
|
||||
"Exponential" = "exp")),
|
||||
|
||||
# br() element to introduce extra vertical spacing ----
|
||||
br(),
|
||||
|
||||
# Input: Slider for the number of observations to generate ----
|
||||
sliderInput("n",
|
||||
"Number of observations:",
|
||||
value = 500,
|
||||
min = 1,
|
||||
max = 1000)
|
||||
|
||||
),
|
||||
|
||||
# Main panel for displaying outputs ----
|
||||
mainPanel(
|
||||
|
||||
# Output: Tabset w/ plot, summary, and table ----
|
||||
tabsetPanel(type = "tabs",
|
||||
tabPanel("Plot", plotOutput("plot")),
|
||||
tabPanel("Summary", verbatimTextOutput("summary")),
|
||||
tabPanel("Table", tableOutput("table"))
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic for random distribution app ----
|
||||
server <- function(input, output) {
|
||||
|
||||
# Reactive expression to generate the requested distribution ----
|
||||
# This is called whenever the inputs change. The output functions
|
||||
# defined below then use the value computed from this expression
|
||||
d <- reactive({
|
||||
dist <- switch(input$dist,
|
||||
norm = rnorm,
|
||||
unif = runif,
|
||||
lnorm = rlnorm,
|
||||
exp = rexp,
|
||||
rnorm)
|
||||
|
||||
dist(input$n)
|
||||
})
|
||||
|
||||
# Generate a plot of the data ----
|
||||
# Also uses the inputs to build the plot label. Note that the
|
||||
# dependencies on the inputs and the data reactive expression are
|
||||
# both tracked, and all expressions are called in the sequence
|
||||
# implied by the dependency graph.
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
hist(d(),
|
||||
main = paste("r", dist, "(", n, ")", sep = ""),
|
||||
col = "#75AADB", border = "white")
|
||||
})
|
||||
|
||||
# Generate a summary of the data ----
|
||||
output$summary <- renderPrint({
|
||||
summary(d())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the data ----
|
||||
output$table <- renderTable({
|
||||
d()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui, server)
|
||||
@@ -1,44 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for random distribution application
|
||||
function(input, output) {
|
||||
|
||||
# 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 expression
|
||||
data <- reactive({
|
||||
dist <- switch(input$dist,
|
||||
norm = rnorm,
|
||||
unif = runif,
|
||||
lnorm = rlnorm,
|
||||
exp = rexp,
|
||||
rnorm)
|
||||
|
||||
dist(input$n)
|
||||
})
|
||||
|
||||
# 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 expression are both tracked, and
|
||||
# all expressions are called in the sequence implied by the
|
||||
# dependency graph
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
hist(data(),
|
||||
main=paste('r', dist, '(', n, ')', sep=''))
|
||||
})
|
||||
|
||||
# Generate a summary of the data
|
||||
output$summary <- renderPrint({
|
||||
summary(data())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the data
|
||||
output$table <- renderTable({
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for random distribution application
|
||||
fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Tabsets"),
|
||||
|
||||
# Sidebar with controls to select the random distribution type
|
||||
# and number of observations to generate. Note the use of the
|
||||
# br() element to introduce extra vertical spacing
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
radioButtons("dist", "Distribution type:",
|
||||
c("Normal" = "norm",
|
||||
"Uniform" = "unif",
|
||||
"Log-normal" = "lnorm",
|
||||
"Exponential" = "exp")),
|
||||
br(),
|
||||
|
||||
sliderInput("n",
|
||||
"Number of observations:",
|
||||
value = 500,
|
||||
min = 1,
|
||||
max = 1000)
|
||||
),
|
||||
|
||||
# Show a tabset that includes a plot, summary, and table view
|
||||
# of the generated distribution
|
||||
mainPanel(
|
||||
tabsetPanel(type = "tabs",
|
||||
tabPanel("Plot", plotOutput("plot")),
|
||||
tabPanel("Summary", verbatimTextOutput("summary")),
|
||||
tabPanel("Table", tableOutput("table"))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
82
inst/examples/07_widgets/app.R
Normal file
82
inst/examples/07_widgets/app.R
Normal file
@@ -0,0 +1,82 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer app ----
|
||||
ui <- fluidPage(
|
||||
|
||||
# App title ----
|
||||
titlePanel("More Widgets"),
|
||||
|
||||
# Sidebar layout with input and output definitions ----
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar panel for inputs ----
|
||||
sidebarPanel(
|
||||
|
||||
# Input: Select a dataset ----
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
# Input: Specify the number of observations to view ----
|
||||
numericInput("obs", "Number of observations to view:", 10),
|
||||
|
||||
# Include clarifying text ----
|
||||
helpText("Note: while the data view will show only the specified",
|
||||
"number of observations, the summary will still be based",
|
||||
"on the full dataset."),
|
||||
|
||||
# Input: actionButton() to defer the rendering of output ----
|
||||
# until the user explicitly clicks the button (rather than
|
||||
# doing it immediately when inputs change). This is useful if
|
||||
# the computations required to render output are inordinately
|
||||
# time-consuming.
|
||||
actionButton("update", "Update View")
|
||||
|
||||
),
|
||||
|
||||
# Main panel for displaying outputs ----
|
||||
mainPanel(
|
||||
|
||||
# Output: Header + summary of distribution ----
|
||||
h4("Summary"),
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
# Output: Header + table of distribution ----
|
||||
h4("Observations"),
|
||||
tableOutput("view")
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic to summarize and view selected dataset ----
|
||||
server <- function(input, output) {
|
||||
|
||||
# Return the requested dataset ----
|
||||
# Note that we use eventReactive() here, which depends on
|
||||
# input$update (the action button), so that the output is only
|
||||
# updated when the user clicks the button
|
||||
datasetInput <- eventReactive(input$update, {
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
}, ignoreNULL = FALSE)
|
||||
|
||||
# Generate a summary of the dataset ----
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations ----
|
||||
# The use of isolate() is necessary because we don't want the table
|
||||
# to update whenever input$obs changes (only when the user clicks
|
||||
# the action button)
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = isolate(input$obs))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui, server)
|
||||
@@ -1,32 +0,0 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the
|
||||
# selected dataset
|
||||
function(input, output) {
|
||||
|
||||
# Return the requested dataset. Note that we use `eventReactive()`
|
||||
# here, which takes a dependency on input$update (the action
|
||||
# button), so that the output is only updated when the user
|
||||
# clicks the button.
|
||||
datasetInput <- eventReactive(input$update, {
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
}, ignoreNULL = FALSE)
|
||||
|
||||
# Generate a summary of the dataset
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations. The use of `isolate()` here
|
||||
# is necessary because we don't want the table to update
|
||||
# whenever input$obs changes (only when the user clicks the
|
||||
# action button).
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = isolate(input$obs))
|
||||
})
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
fluidPage(
|
||||
|
||||
# Application title.
|
||||
titlePanel("More Widgets"),
|
||||
|
||||
# Sidebar with controls to select a dataset and specify the
|
||||
# number of observations to view. The helpText function is
|
||||
# also used to include clarifying text. Most notably, the
|
||||
# inclusion of an actionButton defers the rendering of output
|
||||
# until the user explicitly clicks the button (rather than
|
||||
# doing it immediately when inputs change). This is useful if
|
||||
# the computations required to render output are inordinately
|
||||
# time-consuming.
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
numericInput("obs", "Number of observations to view:", 10),
|
||||
|
||||
helpText("Note: while the data view will show only the specified",
|
||||
"number of observations, the summary will still be based",
|
||||
"on the full dataset."),
|
||||
|
||||
actionButton("update", "Update View")
|
||||
),
|
||||
|
||||
# Show a summary of the dataset and an HTML table with the
|
||||
# requested number of observations. Note the use of the h4
|
||||
# function to provide an additional header above each output
|
||||
# section.
|
||||
mainPanel(
|
||||
h4("Summary"),
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
h4("Observations"),
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1,4 +1 @@
|
||||
Normally we use the built-in functions, such as `textInput()`, to generate
|
||||
the HTML UI in the R script `ui.R`. Actually **shiny** also works with a
|
||||
custom HTML page `www/index.html`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#html-ui) for more details.
|
||||
Normally we use the built-in functions, such as `textInput()`, to generate the HTML UI in the R script `ui.R`. Actually **shiny** also works with a custom HTML page `www/index.html`. See [the tutorial](http://shiny.rstudio.com/tutorial/) for more details.
|
||||
|
||||
47
inst/examples/08_html/app.R
Normal file
47
inst/examples/08_html/app.R
Normal file
@@ -0,0 +1,47 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for random distribution app ----
|
||||
server <- function(input, output) {
|
||||
|
||||
# Reactive expression to generate the requested distribution ----
|
||||
# This is called whenever the inputs change. The output functions
|
||||
# defined below then use the value computed from this expression
|
||||
d <- reactive({
|
||||
dist <- switch(input$dist,
|
||||
norm = rnorm,
|
||||
unif = runif,
|
||||
lnorm = rlnorm,
|
||||
exp = rexp,
|
||||
rnorm)
|
||||
|
||||
dist(input$n)
|
||||
})
|
||||
|
||||
# Generate a plot of the data ----
|
||||
# Also uses the inputs to build the plot label. Note that the
|
||||
# dependencies on the inputs and the data reactive expression are
|
||||
# both tracked, and all expressions are called in the sequence
|
||||
# implied by the dependency graph.
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
hist(d(),
|
||||
main = paste("r", dist, "(", n, ")", sep = ""),
|
||||
col = "#75AADB", border = "white")
|
||||
})
|
||||
|
||||
# Generate a summary of the data ----
|
||||
output$summary <- renderPrint({
|
||||
summary(d())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the head of the data ----
|
||||
output$table <- renderTable({
|
||||
head(data.frame(x = d()))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui = htmlTemplate("www/index.html"), server)
|
||||
@@ -1,42 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for random distribution application
|
||||
function(input, output) {
|
||||
|
||||
# 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,
|
||||
lnorm = rlnorm,
|
||||
exp = rexp,
|
||||
rnorm)
|
||||
|
||||
dist(input$n)
|
||||
})
|
||||
|
||||
# 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 expression are both tracked, and all expressions
|
||||
# are called in the sequence implied by the dependency graph
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
hist(data(),
|
||||
main=paste('r', dist, '(', n, ')', sep=''))
|
||||
})
|
||||
|
||||
# Generate a summary of the data
|
||||
output$summary <- renderPrint({
|
||||
summary(data())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the data
|
||||
output$table <- renderTable({
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
}
|
||||
@@ -3,13 +3,13 @@
|
||||
<head>
|
||||
<script src="shared/jquery.js" type="text/javascript"></script>
|
||||
<script src="shared/shiny.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
<h1>HTML UI</h1>
|
||||
|
||||
|
||||
<p>
|
||||
<label>Distribution type:</label><br />
|
||||
<select name="dist">
|
||||
@@ -17,22 +17,25 @@
|
||||
<option value="unif">Uniform</option>
|
||||
<option value="lnorm">Log-normal</option>
|
||||
<option value="exp">Exponential</option>
|
||||
</select>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
|
||||
<label>Number of observations:</label><br />
|
||||
|
||||
<label>Number of observations:</label><br />
|
||||
<input type="number" name="n" value="500" min="1" max="1000" />
|
||||
|
||||
</p>
|
||||
|
||||
<pre id="summary" class="shiny-text-output"></pre>
|
||||
|
||||
<div id="plot" class="shiny-plot-output"
|
||||
style="width: 100%; height: 400px"></div>
|
||||
|
||||
|
||||
<h3>Summary of data:</h3>
|
||||
<pre id="summary" class="shiny-text-output"></pre>
|
||||
|
||||
<h3>Plot of data:</h3>
|
||||
<div id="plot" class="shiny-plot-output"
|
||||
style="width: 100%; height: 300px"></div>
|
||||
|
||||
<h3>Head of data:</h3>
|
||||
<div id="table" class="shiny-html-output"></div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
We can add a file upload input in the UI using the function `fileInput()`,
|
||||
e.g. `fileInput('foo')`. In `server.R`, we can access the uploaded files via
|
||||
`input$foo`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#uploads) for more details.
|
||||
e.g. `fileInput('foo')`. In the `server` function, we can access the
|
||||
uploaded files via `input$foo`.
|
||||
|
||||
92
inst/examples/09_upload/app.R
Normal file
92
inst/examples/09_upload/app.R
Normal file
@@ -0,0 +1,92 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for data upload app ----
|
||||
ui <- fluidPage(
|
||||
|
||||
# App title ----
|
||||
titlePanel("Uploading Files"),
|
||||
|
||||
# Sidebar layout with input and output definitions ----
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar panel for inputs ----
|
||||
sidebarPanel(
|
||||
|
||||
# Input: Select a file ----
|
||||
fileInput("file1", "Choose CSV File",
|
||||
multiple = TRUE,
|
||||
accept = c("text/csv",
|
||||
"text/comma-separated-values,text/plain",
|
||||
".csv")),
|
||||
|
||||
# Horizontal line ----
|
||||
tags$hr(),
|
||||
|
||||
# Input: Checkbox if file has header ----
|
||||
checkboxInput("header", "Header", TRUE),
|
||||
|
||||
# Input: Select separator ----
|
||||
radioButtons("sep", "Separator",
|
||||
choices = c(Comma = ",",
|
||||
Semicolon = ";",
|
||||
Tab = "\t"),
|
||||
selected = ","),
|
||||
|
||||
# Input: Select quotes ----
|
||||
radioButtons("quote", "Quote",
|
||||
choices = c(None = "",
|
||||
"Double Quote" = '"',
|
||||
"Single Quote" = "'"),
|
||||
selected = '"'),
|
||||
|
||||
# Horizontal line ----
|
||||
tags$hr(),
|
||||
|
||||
# Input: Select number of rows to display ----
|
||||
radioButtons("disp", "Display",
|
||||
choices = c(Head = "head",
|
||||
All = "all"),
|
||||
selected = "head")
|
||||
|
||||
),
|
||||
|
||||
# Main panel for displaying outputs ----
|
||||
mainPanel(
|
||||
|
||||
# Output: Data file ----
|
||||
tableOutput("contents")
|
||||
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic to read selected file ----
|
||||
server <- function(input, output) {
|
||||
|
||||
output$contents <- renderTable({
|
||||
|
||||
# input$file1 will be NULL initially. After the user selects
|
||||
# and uploads a file, head of that data file by default,
|
||||
# or all rows if selected, will be shown.
|
||||
|
||||
req(input$file1)
|
||||
|
||||
df <- read.csv(input$file1$datapath,
|
||||
header = input$header,
|
||||
sep = input$sep,
|
||||
quote = input$quote)
|
||||
|
||||
if(input$disp == "head") {
|
||||
return(head(df))
|
||||
}
|
||||
else {
|
||||
return(df)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui, server)
|
||||
@@ -1,20 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
function(input, output) {
|
||||
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 '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$datapath, header=input$header, sep=input$sep,
|
||||
quote=input$quote)
|
||||
})
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
library(shiny)
|
||||
|
||||
fluidPage(
|
||||
titlePanel("Uploading Files"),
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
fileInput('file1', 'Choose CSV File',
|
||||
accept=c('text/csv',
|
||||
'text/comma-separated-values,text/plain',
|
||||
'.csv')),
|
||||
tags$hr(),
|
||||
checkboxInput('header', 'Header', TRUE),
|
||||
radioButtons('sep', 'Separator',
|
||||
c(Comma=',',
|
||||
Semicolon=';',
|
||||
Tab='\t'),
|
||||
','),
|
||||
radioButtons('quote', 'Quote',
|
||||
c(None='',
|
||||
'Double Quote'='"',
|
||||
'Single Quote'="'"),
|
||||
'"')
|
||||
),
|
||||
mainPanel(
|
||||
tableOutput('contents')
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1,4 +1,2 @@
|
||||
We can add a download button to the UI using `downloadButton()`, and write
|
||||
the content of the file in `downloadHandler()` in `server.R`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#downloads) for more
|
||||
details.
|
||||
the content of the file in `downloadHandler()` in the `server` function.
|
||||
|
||||
63
inst/examples/10_download/app.R
Normal file
63
inst/examples/10_download/app.R
Normal file
@@ -0,0 +1,63 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for data download app ----
|
||||
ui <- fluidPage(
|
||||
|
||||
# App title ----
|
||||
titlePanel("Downloading Data"),
|
||||
|
||||
# Sidebar layout with input and output definitions ----
|
||||
sidebarLayout(
|
||||
|
||||
# Sidebar panel for inputs ----
|
||||
sidebarPanel(
|
||||
|
||||
# Input: Choose dataset ----
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
|
||||
# Button
|
||||
downloadButton("downloadData", "Download")
|
||||
|
||||
),
|
||||
|
||||
# Main panel for displaying outputs ----
|
||||
mainPanel(
|
||||
|
||||
tableOutput("table")
|
||||
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
# Define server logic to display and download selected file ----
|
||||
server <- function(input, output) {
|
||||
|
||||
# Reactive value for selected dataset ----
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# Table of selected dataset ----
|
||||
output$table <- renderTable({
|
||||
datasetInput()
|
||||
})
|
||||
|
||||
# Downloadable csv of selected dataset ----
|
||||
output$downloadData <- downloadHandler(
|
||||
filename = function() {
|
||||
paste(input$dataset, ".csv", sep = "")
|
||||
},
|
||||
content = function(file) {
|
||||
write.csv(datasetInput(), file, row.names = FALSE)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui, server)
|
||||
@@ -1,21 +0,0 @@
|
||||
function(input, output) {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
output$table <- renderTable({
|
||||
datasetInput()
|
||||
})
|
||||
|
||||
output$downloadData <- downloadHandler(
|
||||
filename = function() {
|
||||
paste(input$dataset, '.csv', sep='')
|
||||
},
|
||||
content = function(file) {
|
||||
write.csv(datasetInput(), file)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
fluidPage(
|
||||
titlePanel('Downloading Data'),
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
downloadButton('downloadData', 'Download')
|
||||
),
|
||||
mainPanel(
|
||||
tableOutput('table')
|
||||
)
|
||||
)
|
||||
)
|
||||
21
inst/examples/11_timer/app.R
Normal file
21
inst/examples/11_timer/app.R
Normal file
@@ -0,0 +1,21 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for displaying current time ----
|
||||
ui <- fluidPage(
|
||||
|
||||
h2(textOutput("currentTime"))
|
||||
|
||||
)
|
||||
|
||||
# Define server logic to show current time, update every second ----
|
||||
server <- function(input, output, session) {
|
||||
|
||||
output$currentTime <- renderText({
|
||||
invalidateLater(1000, session)
|
||||
paste("The current time is", Sys.time())
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
# Create Shiny app ----
|
||||
shinyApp(ui, server)
|
||||
@@ -1,6 +0,0 @@
|
||||
function(input, output, session) {
|
||||
output$currentTime <- renderText({
|
||||
invalidateLater(1000, session)
|
||||
paste("The current time is", Sys.time())
|
||||
})
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fluidPage(
|
||||
textOutput("currentTime")
|
||||
)
|
||||
@@ -57,6 +57,8 @@ sd_section("UI Inputs",
|
||||
"updateSelectInput",
|
||||
"updateSliderInput",
|
||||
"updateTabsetPanel",
|
||||
"insertTab",
|
||||
"showTab",
|
||||
"updateTextInput",
|
||||
"updateTextAreaInput",
|
||||
"updateQueryString",
|
||||
@@ -154,7 +156,8 @@ sd_section("Running",
|
||||
"runGadget",
|
||||
"runUrl",
|
||||
"stopApp",
|
||||
"viewer"
|
||||
"viewer",
|
||||
"isRunning"
|
||||
)
|
||||
)
|
||||
sd_section("Bookmarking state",
|
||||
@@ -193,12 +196,16 @@ sd_section("Utility functions",
|
||||
"parseQueryString",
|
||||
"plotPNG",
|
||||
"exportTestValues",
|
||||
"setSerializer",
|
||||
"snapshotExclude",
|
||||
"snapshotPreprocessInput",
|
||||
"snapshotPreprocessOutput",
|
||||
"markOutputAttrs",
|
||||
"repeatable",
|
||||
"shinyDeprecated",
|
||||
"serverInfo",
|
||||
"shiny-options"
|
||||
"shiny-options",
|
||||
"onStop"
|
||||
)
|
||||
)
|
||||
sd_section("Plot interaction",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
||||
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
||||
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400,600' rel='stylesheet' type='text/css'>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
||||
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
||||
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400,600' rel='stylesheet' type='text/css'>
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
|
||||
@@ -373,3 +373,11 @@ pre.shiny-text-output.noplaceholder:empty {
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.shiny-file-input-active {
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
|
||||
}
|
||||
|
||||
.shiny-file-input-over {
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(76, 174, 76, .6);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
8
inst/www/shared/shiny.min.js
vendored
8
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -15,7 +15,8 @@ checkboxGroupInput(inputId, label, choices = NULL, selected = NULL,
|
||||
\item{choices}{List of values to show checkboxes for. If elements of the list
|
||||
are named then that name rather than the value is displayed to the user. If
|
||||
this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
must not be provided, and vice-versa.}
|
||||
must not be provided, and vice-versa. The values should be strings; other
|
||||
types (such as logicals and numbers) will be coerced to strings.}
|
||||
|
||||
\item{selected}{The values that should be initially selected, if any.}
|
||||
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
\alias{conditionalPanel}
|
||||
\title{Conditional Panel}
|
||||
\usage{
|
||||
conditionalPanel(condition, ...)
|
||||
conditionalPanel(condition, ..., ns = NS(NULL))
|
||||
}
|
||||
\arguments{
|
||||
\item{condition}{A JavaScript expression that will be evaluated repeatedly to
|
||||
determine whether the panel should be displayed.}
|
||||
|
||||
\item{...}{Elements to include in the panel.}
|
||||
|
||||
\item{ns}{The \code{\link[=NS]{namespace}} object of the current module, if
|
||||
any.}
|
||||
}
|
||||
\description{
|
||||
Creates a panel that is visible or not, depending on the value of a
|
||||
@@ -32,27 +35,50 @@ You are not recommended to use special JavaScript characters such as a
|
||||
value.
|
||||
}
|
||||
\examples{
|
||||
sidebarPanel(
|
||||
selectInput(
|
||||
"plotType", "Plot Type",
|
||||
c(Scatter = "scatter",
|
||||
Histogram = "hist")),
|
||||
|
||||
# Only show this panel if the plot type is a histogram
|
||||
conditionalPanel(
|
||||
condition = "input.plotType == 'hist'",
|
||||
selectInput(
|
||||
"breaks", "Breaks",
|
||||
c("Sturges",
|
||||
"Scott",
|
||||
"Freedman-Diaconis",
|
||||
"[Custom]" = "custom")),
|
||||
|
||||
# Only show this panel if Custom is selected
|
||||
## Only run this example in interactive R sessions
|
||||
if (interactive()) {
|
||||
ui <- fluidPage(
|
||||
sidebarPanel(
|
||||
selectInput("plotType", "Plot Type",
|
||||
c(Scatter = "scatter", Histogram = "hist")
|
||||
),
|
||||
# Only show this panel if the plot type is a histogram
|
||||
conditionalPanel(
|
||||
condition = "input.breaks == 'custom'",
|
||||
sliderInput("breakCount", "Break Count", min=1, max=1000, value=10)
|
||||
condition = "input.plotType == 'hist'",
|
||||
selectInput(
|
||||
"breaks", "Breaks",
|
||||
c("Sturges", "Scott", "Freedman-Diaconis", "[Custom]" = "custom")
|
||||
),
|
||||
# Only show this panel if Custom is selected
|
||||
conditionalPanel(
|
||||
condition = "input.breaks == 'custom'",
|
||||
sliderInput("breakCount", "Break Count", min = 1, max = 50, value = 10)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
mainPanel(
|
||||
plotOutput("plot")
|
||||
)
|
||||
)
|
||||
|
||||
server <- function(input, output) {
|
||||
x <- rnorm(100)
|
||||
y <- rnorm(100)
|
||||
|
||||
output$plot <- renderPlot({
|
||||
if (input$plotType == "scatter") {
|
||||
plot(x, y)
|
||||
} else {
|
||||
breaks <- input$breaks
|
||||
if (breaks == "custom") {
|
||||
breaks <- input$breakCount
|
||||
}
|
||||
|
||||
hist(x, breaks = breaks)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,18 @@
|
||||
\alias{createWebDependency}
|
||||
\title{Create a web dependency}
|
||||
\usage{
|
||||
createWebDependency(dependency)
|
||||
createWebDependency(dependency, scrubFile = TRUE)
|
||||
}
|
||||
\arguments{
|
||||
\item{dependency}{A single HTML dependency object, created using
|
||||
\code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named, then
|
||||
\code{href} and/or \code{file} names must be present.}
|
||||
\code{\link[htmltools]{htmlDependency}}. If the \code{src} value is named,
|
||||
then \code{href} and/or \code{file} names must be present.}
|
||||
|
||||
\item{scrubFile}{If TRUE (the default), remove \code{src$file} for the
|
||||
dependency. This prevents the local file path from being sent to the client
|
||||
when dynamic web dependencies are used. If FALSE, don't remove
|
||||
\code{src$file}. Setting it to FALSE should be needed only in very unusual
|
||||
cases.}
|
||||
}
|
||||
\value{
|
||||
A single HTML dependency object that has an \code{href}-named element
|
||||
|
||||
148
man/insertTab.Rd
Normal file
148
man/insertTab.Rd
Normal file
@@ -0,0 +1,148 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/insert-tab.R
|
||||
\name{insertTab}
|
||||
\alias{insertTab}
|
||||
\alias{prependTab}
|
||||
\alias{appendTab}
|
||||
\alias{removeTab}
|
||||
\title{Dynamically insert/remove a tabPanel}
|
||||
\usage{
|
||||
insertTab(inputId, tab, target, position = c("before", "after"),
|
||||
select = FALSE, session = getDefaultReactiveDomain())
|
||||
|
||||
prependTab(inputId, tab, select = FALSE, menuName = NULL,
|
||||
session = getDefaultReactiveDomain())
|
||||
|
||||
appendTab(inputId, tab, select = FALSE, menuName = NULL,
|
||||
session = getDefaultReactiveDomain())
|
||||
|
||||
removeTab(inputId, target, session = getDefaultReactiveDomain())
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{The \code{id} of the \code{tabsetPanel} (or
|
||||
\code{navlistPanel} or \code{navbarPage}) into which \code{tab} will
|
||||
be inserted/removed.}
|
||||
|
||||
\item{tab}{The item to be added (must be created with \code{tabPanel},
|
||||
or with \code{navbarMenu}).}
|
||||
|
||||
\item{target}{If inserting: the \code{value} of an existing
|
||||
\code{tabPanel}, next to which \code{tab} will be added.
|
||||
If removing: the \code{value} of the \code{tabPanel} that
|
||||
you want to remove. See Details if you want to insert next to/remove
|
||||
an entire \code{navbarMenu} instead.}
|
||||
|
||||
\item{position}{Should \code{tab} be added before or after the
|
||||
\code{target} tab?}
|
||||
|
||||
\item{select}{Should \code{tab} be selected upon being inserted?}
|
||||
|
||||
\item{session}{The shiny session within which to call this function.}
|
||||
|
||||
\item{menuName}{This argument should only be used when you want to
|
||||
prepend (or append) \code{tab} to the beginning (or end) of an
|
||||
existing \code{\link{navbarMenu}} (which must itself be part of
|
||||
an existing \code{\link{navbarPage}}). In this case, this argument
|
||||
should be the \code{menuName} that you gave your \code{navbarMenu}
|
||||
when you first created it (by default, this is equal to the value
|
||||
of the \code{title} argument). Note that you still need to set the
|
||||
\code{inputId} argument to whatever the \code{id} of the parent
|
||||
\code{navbarPage} is. If \code{menuName} is left as \code{NULL},
|
||||
\code{tab} will be prepended (or appended) to whatever
|
||||
\code{inputId} is.}
|
||||
}
|
||||
\description{
|
||||
Dynamically insert or remove a \code{\link{tabPanel}} (or a
|
||||
\code{\link{navbarMenu}}) from an existing \code{\link{tabsetPanel}},
|
||||
\code{\link{navlistPanel}} or \code{\link{navbarPage}}.
|
||||
}
|
||||
\details{
|
||||
When you want to insert a new tab before or after an existing tab, you
|
||||
should use \code{insertTab}. When you want to prepend a tab (i.e. add a
|
||||
tab to the beginning of the \code{tabsetPanel}), use \code{prependTab}.
|
||||
When you want to append a tab (i.e. add a tab to the end of the
|
||||
\code{tabsetPanel}), use \code{appendTab}.
|
||||
|
||||
For \code{navbarPage}, you can insert/remove conventional
|
||||
\code{tabPanel}s (whether at the top level or nested inside a
|
||||
\code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
|
||||
For the latter case, \code{target} should be the \code{menuName} that
|
||||
you gave your \code{navbarMenu} when you first created it (by default,
|
||||
this is equal to the value of the \code{title} argument).
|
||||
}
|
||||
\examples{
|
||||
## Only run this example in interactive R sessions
|
||||
if (interactive()) {
|
||||
|
||||
# example app for inserting/removing a tab
|
||||
ui <- fluidPage(
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
actionButton("add", "Add 'Dynamic' tab"),
|
||||
actionButton("remove", "Remove 'Foo' tab")
|
||||
),
|
||||
mainPanel(
|
||||
tabsetPanel(id = "tabs",
|
||||
tabPanel("Hello", "This is the hello tab"),
|
||||
tabPanel("Foo", "This is the foo tab"),
|
||||
tabPanel("Bar", "This is the bar tab")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
server <- function(input, output, session) {
|
||||
observeEvent(input$add, {
|
||||
insertTab(inputId = "tabs",
|
||||
tabPanel("Dynamic", "This a dynamically-added tab"),
|
||||
target = "Bar"
|
||||
)
|
||||
})
|
||||
observeEvent(input$remove, {
|
||||
removeTab(inputId = "tabs", target = "Foo")
|
||||
})
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
|
||||
|
||||
# example app for prepending/appending a navbarMenu
|
||||
ui <- navbarPage("Navbar page", id = "tabs",
|
||||
tabPanel("Home",
|
||||
actionButton("prepend", "Prepend a navbarMenu"),
|
||||
actionButton("append", "Append a navbarMenu")
|
||||
)
|
||||
)
|
||||
server <- function(input, output, session) {
|
||||
observeEvent(input$prepend, {
|
||||
id <- paste0("Dropdown", input$prepend, "p")
|
||||
prependTab(inputId = "tabs",
|
||||
navbarMenu(id,
|
||||
tabPanel("Drop1", paste("Drop1 page from", id)),
|
||||
tabPanel("Drop2", paste("Drop2 page from", id)),
|
||||
"------",
|
||||
"Header",
|
||||
tabPanel("Drop3", paste("Drop3 page from", id))
|
||||
)
|
||||
)
|
||||
})
|
||||
observeEvent(input$append, {
|
||||
id <- paste0("Dropdown", input$append, "a")
|
||||
appendTab(inputId = "tabs",
|
||||
navbarMenu(id,
|
||||
tabPanel("Drop1", paste("Drop1 page from", id)),
|
||||
tabPanel("Drop2", paste("Drop2 page from", id)),
|
||||
"------",
|
||||
"Header",
|
||||
tabPanel("Drop3", paste("Drop3 page from", id))
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{showTab}}
|
||||
}
|
||||
15
man/isRunning.Rd
Normal file
15
man/isRunning.Rd
Normal file
@@ -0,0 +1,15 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/server.R
|
||||
\name{isRunning}
|
||||
\alias{isRunning}
|
||||
\title{Check whether a Shiny application is running}
|
||||
\usage{
|
||||
isRunning()
|
||||
}
|
||||
\value{
|
||||
\code{TRUE} if a Shiny application is currently running. Otherwise,
|
||||
\code{FALSE}.
|
||||
}
|
||||
\description{
|
||||
This function tests whether a Shiny application is currently running.
|
||||
}
|
||||
@@ -4,13 +4,17 @@
|
||||
\alias{markOutputAttrs}
|
||||
\title{Mark a render function with attributes that will be used by the output}
|
||||
\usage{
|
||||
markOutputAttrs(renderFunc, snapshotExclude = NULL)
|
||||
markOutputAttrs(renderFunc, snapshotExclude = NULL,
|
||||
snapshotPreprocess = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{renderFunc}{A function that is suitable for assigning to a Shiny output
|
||||
slot.}
|
||||
|
||||
\item{snapshotExclude}{If TRUE, exclude the output from test snapshots.}
|
||||
|
||||
\item{snapshotPreprocess}{A function for preprocessing the value before
|
||||
taking a test snapshot.}
|
||||
}
|
||||
\description{
|
||||
Mark a render function with attributes that will be used by the output
|
||||
|
||||
@@ -10,7 +10,7 @@ navbarPage(title, ..., id = NULL, selected = NULL,
|
||||
footer = NULL, inverse = FALSE, collapsible = FALSE, collapsable,
|
||||
fluid = TRUE, responsive = NULL, theme = NULL, windowTitle = title)
|
||||
|
||||
navbarMenu(title, ..., icon = NULL)
|
||||
navbarMenu(title, ..., menuName = title, icon = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{title}{The title to display in the navbar}
|
||||
@@ -65,6 +65,10 @@ www directory). For example, to use the theme located at
|
||||
\item{windowTitle}{The title that should be displayed by the browser window.
|
||||
Useful if \code{title} is not a string.}
|
||||
|
||||
\item{menuName}{A name that identifies this \code{navbarMenu}. This
|
||||
is needed if you want to insert/remove or show/hide an entire
|
||||
\code{navbarMenu}.}
|
||||
|
||||
\item{icon}{Optional icon to appear on a \code{navbarMenu} tab.}
|
||||
}
|
||||
\value{
|
||||
@@ -98,5 +102,6 @@ navbarPage("App Title",
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{tabPanel}}, \code{\link{tabsetPanel}},
|
||||
\code{\link{updateNavbarPage}}
|
||||
\code{\link{updateNavbarPage}}, \code{\link{insertTab}},
|
||||
\code{\link{showTab}}
|
||||
}
|
||||
|
||||
@@ -53,5 +53,6 @@ fluidPage(
|
||||
)
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{tabPanel}}, \code{\link{updateNavlistPanel}}
|
||||
\code{\link{tabPanel}}, \code{\link{updateNavlistPanel}},
|
||||
\code{\link{insertTab}}, \code{\link{showTab}}
|
||||
}
|
||||
|
||||
@@ -34,3 +34,7 @@ These functions should be called within the application's server function.
|
||||
All of these functions return a function which can be called with no
|
||||
arguments to cancel the registration.
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{onStop}()} for registering callbacks that will be
|
||||
invoked when the application exits, or when a session ends.
|
||||
}
|
||||
|
||||
81
man/onStop.Rd
Normal file
81
man/onStop.Rd
Normal file
@@ -0,0 +1,81 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/shiny.R
|
||||
\name{onStop}
|
||||
\alias{onStop}
|
||||
\title{Run code after an application or session ends}
|
||||
\usage{
|
||||
onStop(fun, session = getDefaultReactiveDomain())
|
||||
}
|
||||
\arguments{
|
||||
\item{fun}{A function that will be called after the app has finished running.}
|
||||
|
||||
\item{session}{A scope for when the callback will run. If \code{onStop} is
|
||||
called from within the server function, this will default to the current
|
||||
session, and the callback will be invoked when the current session ends. If
|
||||
\code{onStop} is called outside a server function, then the callback will
|
||||
be invoked with the application exits.}
|
||||
}
|
||||
\value{
|
||||
A function which, if invoked, will cancel the callback.
|
||||
}
|
||||
\description{
|
||||
This function registers callback functions that are invoked when the
|
||||
application exits (when \code{\link{runApp}} exits), or after each user
|
||||
session ends (when a client disconnects).
|
||||
}
|
||||
\examples{
|
||||
## Only run this example in interactive R sessions
|
||||
if (interactive()) {
|
||||
# Open this application in multiple browsers, then close the browsers.
|
||||
shinyApp(
|
||||
ui = basicPage("onStop demo"),
|
||||
|
||||
server = function(input, output, session) {
|
||||
onStop(function() cat("Session stopped\\n"))
|
||||
},
|
||||
|
||||
onStart = function() {
|
||||
cat("Doing application setup\\n")
|
||||
|
||||
onStop(function() {
|
||||
cat("Doing application cleanup\\n")
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
# In the example above, onStop() is called inside of onStart(). This is
|
||||
# the pattern that should be used when creating a shinyApp() object from
|
||||
# a function, or at the console. If instead you are writing an app.R which
|
||||
# will be invoked with runApp(), you can do it that way, or put the onStop()
|
||||
# before the shinyApp() call, as shown below.
|
||||
|
||||
\dontrun{
|
||||
# ==== app.R ====
|
||||
cat("Doing application setup\\n")
|
||||
onStop(function() {
|
||||
cat("Doing application cleanup\\n")
|
||||
})
|
||||
|
||||
shinyApp(
|
||||
ui = basicPage("onStop demo"),
|
||||
|
||||
server = function(input, output, session) {
|
||||
onStop(function() cat("Session stopped\\n"))
|
||||
}
|
||||
)
|
||||
# ==== end app.R ====
|
||||
|
||||
|
||||
# Similarly, if you have a global.R, you can call onStop() from there.
|
||||
# ==== global.R ====
|
||||
cat("Doing application setup\\n")
|
||||
onStop(function() {
|
||||
cat("Doing application cleanup\\n")
|
||||
})
|
||||
# ==== end global.R ====
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{onSessionEnded}()} for the same functionality, but at
|
||||
the session level only.
|
||||
}
|
||||
@@ -15,25 +15,26 @@ radioButtons(inputId, label, choices = NULL, selected = NULL,
|
||||
\item{choices}{List of values to select from (if elements of the list are
|
||||
named then that name rather than the value is displayed to the user). If
|
||||
this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
must not be provided, and vice-versa.}
|
||||
must not be provided, and vice-versa. The values should be strings; other
|
||||
types (such as logicals and numbers) will be coerced to strings.}
|
||||
|
||||
\item{selected}{The initially selected value (if not specified then
|
||||
defaults to the first value)}
|
||||
\item{selected}{The initially selected value (if not specified then defaults
|
||||
to the first value)}
|
||||
|
||||
\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)}
|
||||
|
||||
\item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
see \code{\link{validateCssUnit}}.}
|
||||
|
||||
\item{choiceNames, choiceValues}{List of names and values, respectively,
|
||||
that are displayed to the user in the app and correspond to the each
|
||||
choice (for this reason, \code{choiceNames} and \code{choiceValues}
|
||||
must have the same length). If either of these arguments is
|
||||
provided, then the other \emph{must} be provided and \code{choices}
|
||||
\emph{must not} be provided. The advantage of using both of these over
|
||||
a named list for \code{choices} is that \code{choiceNames} allows any
|
||||
type of UI object to be passed through (tag objects, icons, HTML code,
|
||||
...), instead of just simple text. See Examples.}
|
||||
\item{choiceNames, choiceValues}{List of names and values, respectively, that
|
||||
are displayed to the user in the app and correspond to the each choice (for
|
||||
this reason, \code{choiceNames} and \code{choiceValues} must have the same
|
||||
length). If either of these arguments is provided, then the other
|
||||
\emph{must} be provided and \code{choices} \emph{must not} be provided. The
|
||||
advantage of using both of these over a named list for \code{choices} is
|
||||
that \code{choiceNames} allows any type of UI object to be passed through
|
||||
(tag objects, icons, HTML code, ...), instead of just simple text. See
|
||||
Examples.}
|
||||
}
|
||||
\value{
|
||||
A set of radio buttons that can be added to a UI definition.
|
||||
@@ -43,11 +44,10 @@ Create a set of radio buttons used to select an item from a list.
|
||||
}
|
||||
\details{
|
||||
If you need to represent a "None selected" state, it's possible to default
|
||||
the radio buttons to have no options selected by using
|
||||
\code{selected = character(0)}. However, this is not recommended, as it gives
|
||||
the user no way to return to that state once they've made a selection.
|
||||
Instead, consider having the first of your choices be \code{c("None selected"
|
||||
= "")}.
|
||||
the radio buttons to have no options selected by using \code{selected =
|
||||
character(0)}. However, this is not recommended, as it gives the user no way
|
||||
to return to that state once they've made a selection. Instead, consider
|
||||
having the first of your choices be \code{c("None selected" = "")}.
|
||||
}
|
||||
\examples{
|
||||
## Only run examples in interactive R sessions
|
||||
|
||||
@@ -57,9 +57,22 @@ will be executed in a reactive context; therefore, they may read reactive
|
||||
values and reactive expressions.
|
||||
}
|
||||
\examples{
|
||||
# Assume the existence of readTimestamp and readValue functions
|
||||
function(input, output, session) {
|
||||
data <- reactivePoll(1000, session, readTimestamp, readValue)
|
||||
|
||||
data <- reactivePoll(1000, session,
|
||||
# This function returns the time that log_file was last modified
|
||||
checkFunc = function() {
|
||||
if (file.exists(log_file))
|
||||
file.info(log_file)$mtime[1]
|
||||
else
|
||||
""
|
||||
},
|
||||
# This function returns the content of log_file
|
||||
valueFunc = function() {
|
||||
read.csv(log_file)
|
||||
}
|
||||
)
|
||||
|
||||
output$dataTable <- renderTable({
|
||||
data()
|
||||
})
|
||||
|
||||
20
man/setSerializer.Rd
Normal file
20
man/setSerializer.Rd
Normal file
@@ -0,0 +1,20 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/serializers.R
|
||||
\name{setSerializer}
|
||||
\alias{setSerializer}
|
||||
\title{Add a function for serializing an input before bookmarking application state}
|
||||
\usage{
|
||||
setSerializer(inputId, fun, session = getDefaultReactiveDomain())
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{Name of the input value.}
|
||||
|
||||
\item{fun}{A function that takes the input value and returns a modified
|
||||
value. The returned value will be used for the test snapshot.}
|
||||
|
||||
\item{session}{A Shiny session object.}
|
||||
}
|
||||
\description{
|
||||
Add a function for serializing an input before bookmarking application state
|
||||
}
|
||||
\keyword{internal}
|
||||
@@ -8,7 +8,7 @@
|
||||
\description{
|
||||
Shiny makes it incredibly easy to build interactive web applications with R.
|
||||
Automatic "reactive" binding between inputs and outputs and extensive
|
||||
pre-built widgets make it possible to build beautiful, responsive, and
|
||||
prebuilt widgets make it possible to build beautiful, responsive, and
|
||||
powerful applications with minimal effort.
|
||||
}
|
||||
\details{
|
||||
|
||||
85
man/showTab.Rd
Normal file
85
man/showTab.Rd
Normal file
@@ -0,0 +1,85 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/insert-tab.R
|
||||
\name{showTab}
|
||||
\alias{showTab}
|
||||
\alias{hideTab}
|
||||
\title{Dynamically hide/show a tabPanel}
|
||||
\usage{
|
||||
showTab(inputId, target, select = FALSE,
|
||||
session = getDefaultReactiveDomain())
|
||||
|
||||
hideTab(inputId, target, session = getDefaultReactiveDomain())
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{The \code{id} of the \code{tabsetPanel} (or
|
||||
\code{navlistPanel} or \code{navbarPage}) in which to find
|
||||
\code{target}.}
|
||||
|
||||
\item{target}{The \code{value} of the \code{tabPanel} to be
|
||||
hidden/shown. See Details if you want to hide/show an entire
|
||||
\code{navbarMenu} instead.}
|
||||
|
||||
\item{select}{Should \code{target} be selected upon being shown?}
|
||||
|
||||
\item{session}{The shiny session within which to call this function.}
|
||||
}
|
||||
\description{
|
||||
Dynamically hide or show a \code{\link{tabPanel}} (or a
|
||||
\code{\link{navbarMenu}})from an existing \code{\link{tabsetPanel}},
|
||||
\code{\link{navlistPanel}} or \code{\link{navbarPage}}.
|
||||
}
|
||||
\details{
|
||||
For \code{navbarPage}, you can hide/show conventional
|
||||
\code{tabPanel}s (whether at the top level or nested inside a
|
||||
\code{navbarMenu}), as well as an entire \code{\link{navbarMenu}}.
|
||||
For the latter case, \code{target} should be the \code{menuName} that
|
||||
you gave your \code{navbarMenu} when you first created it (by default,
|
||||
this is equal to the value of the \code{title} argument).
|
||||
}
|
||||
\examples{
|
||||
## Only run this example in interactive R sessions
|
||||
if (interactive()) {
|
||||
|
||||
ui <- navbarPage("Navbar page", id = "tabs",
|
||||
tabPanel("Home",
|
||||
actionButton("hideTab", "Hide 'Foo' tab"),
|
||||
actionButton("showTab", "Show 'Foo' tab"),
|
||||
actionButton("hideMenu", "Hide 'More' navbarMenu"),
|
||||
actionButton("showMenu", "Show 'More' navbarMenu")
|
||||
),
|
||||
tabPanel("Foo", "This is the foo tab"),
|
||||
tabPanel("Bar", "This is the bar tab"),
|
||||
navbarMenu("More",
|
||||
tabPanel("Table", "Table page"),
|
||||
tabPanel("About", "About page"),
|
||||
"------",
|
||||
"Even more!",
|
||||
tabPanel("Email", "Email page")
|
||||
)
|
||||
)
|
||||
|
||||
server <- function(input, output, session) {
|
||||
observeEvent(input$hideTab, {
|
||||
hideTab(inputId = "tabs", target = "Foo")
|
||||
})
|
||||
|
||||
observeEvent(input$showTab, {
|
||||
showTab(inputId = "tabs", target = "Foo")
|
||||
})
|
||||
|
||||
observeEvent(input$hideMenu, {
|
||||
hideTab(inputId = "tabs", target = "More")
|
||||
})
|
||||
|
||||
observeEvent(input$showMenu, {
|
||||
showTab(inputId = "tabs", target = "More")
|
||||
})
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
}
|
||||
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{insertTab}}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/shiny.R
|
||||
% Please edit documentation in R/snapshot.R
|
||||
\name{snapshotExclude}
|
||||
\alias{snapshotExclude}
|
||||
\title{Mark an output to be excluded from test snapshots}
|
||||
|
||||
19
man/snapshotPreprocessInput.Rd
Normal file
19
man/snapshotPreprocessInput.Rd
Normal file
@@ -0,0 +1,19 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/snapshot.R
|
||||
\name{snapshotPreprocessInput}
|
||||
\alias{snapshotPreprocessInput}
|
||||
\title{Add a function for preprocessing an input before taking a test snapshot}
|
||||
\usage{
|
||||
snapshotPreprocessInput(inputId, fun, session = getDefaultReactiveDomain())
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{Name of the input value.}
|
||||
|
||||
\item{fun}{A function that takes the input value and returns a modified
|
||||
value. The returned value will be used for the test snapshot.}
|
||||
|
||||
\item{session}{A Shiny session object.}
|
||||
}
|
||||
\description{
|
||||
Add a function for preprocessing an input before taking a test snapshot
|
||||
}
|
||||
17
man/snapshotPreprocessOutput.Rd
Normal file
17
man/snapshotPreprocessOutput.Rd
Normal file
@@ -0,0 +1,17 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/snapshot.R
|
||||
\name{snapshotPreprocessOutput}
|
||||
\alias{snapshotPreprocessOutput}
|
||||
\title{Add a function for preprocessing an output before taking a test snapshot}
|
||||
\usage{
|
||||
snapshotPreprocessOutput(x, fun)
|
||||
}
|
||||
\arguments{
|
||||
\item{x}{A reactive which will be assigned to an output.}
|
||||
|
||||
\item{fun}{A function that takes the output value as an input and returns a
|
||||
modified value. The returned value will be used for the test snapshot.}
|
||||
}
|
||||
\description{
|
||||
Add a function for preprocessing an output before taking a test snapshot
|
||||
}
|
||||
@@ -44,5 +44,6 @@ mainPanel(
|
||||
)
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{tabPanel}}, \code{\link{updateTabsetPanel}}
|
||||
\code{\link{tabPanel}}, \code{\link{updateTabsetPanel}},
|
||||
\code{\link{insertTab}}, \code{\link{showTab}}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ updateCheckboxGroupInput(session, inputId, label = NULL, choices = NULL,
|
||||
\item{choices}{List of values to show checkboxes for. If elements of the list
|
||||
are named then that name rather than the value is displayed to the user. If
|
||||
this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
must not be provided, and vice-versa.}
|
||||
must not be provided, and vice-versa. The values should be strings; other
|
||||
types (such as logicals and numbers) will be coerced to strings.}
|
||||
|
||||
\item{selected}{The values that should be initially selected, if any.}
|
||||
|
||||
|
||||
@@ -19,32 +19,33 @@ updateRadioButtons(session, inputId, label = NULL, choices = NULL,
|
||||
\item{choices}{List of values to select from (if elements of the list are
|
||||
named then that name rather than the value is displayed to the user). If
|
||||
this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
must not be provided, and vice-versa.}
|
||||
must not be provided, and vice-versa. The values should be strings; other
|
||||
types (such as logicals and numbers) will be coerced to strings.}
|
||||
|
||||
\item{selected}{The initially selected value (if not specified then
|
||||
defaults to the first value)}
|
||||
\item{selected}{The initially selected value (if not specified then defaults
|
||||
to the first value)}
|
||||
|
||||
\item{inline}{If \code{TRUE}, render the choices inline (i.e. horizontally)}
|
||||
|
||||
\item{choiceNames}{List of names and values, respectively,
|
||||
that are displayed to the user in the app and correspond to the each
|
||||
choice (for this reason, \code{choiceNames} and \code{choiceValues}
|
||||
must have the same length). If either of these arguments is
|
||||
provided, then the other \emph{must} be provided and \code{choices}
|
||||
\emph{must not} be provided. The advantage of using both of these over
|
||||
a named list for \code{choices} is that \code{choiceNames} allows any
|
||||
type of UI object to be passed through (tag objects, icons, HTML code,
|
||||
...), instead of just simple text. See Examples.}
|
||||
\item{choiceNames}{List of names and values, respectively, that
|
||||
are displayed to the user in the app and correspond to the each choice (for
|
||||
this reason, \code{choiceNames} and \code{choiceValues} must have the same
|
||||
length). If either of these arguments is provided, then the other
|
||||
\emph{must} be provided and \code{choices} \emph{must not} be provided. The
|
||||
advantage of using both of these over a named list for \code{choices} is
|
||||
that \code{choiceNames} allows any type of UI object to be passed through
|
||||
(tag objects, icons, HTML code, ...), instead of just simple text. See
|
||||
Examples.}
|
||||
|
||||
\item{choiceValues}{List of names and values, respectively,
|
||||
that are displayed to the user in the app and correspond to the each
|
||||
choice (for this reason, \code{choiceNames} and \code{choiceValues}
|
||||
must have the same length). If either of these arguments is
|
||||
provided, then the other \emph{must} be provided and \code{choices}
|
||||
\emph{must not} be provided. The advantage of using both of these over
|
||||
a named list for \code{choices} is that \code{choiceNames} allows any
|
||||
type of UI object to be passed through (tag objects, icons, HTML code,
|
||||
...), instead of just simple text. See Examples.}
|
||||
\item{choiceValues}{List of names and values, respectively, that
|
||||
are displayed to the user in the app and correspond to the each choice (for
|
||||
this reason, \code{choiceNames} and \code{choiceValues} must have the same
|
||||
length). If either of these arguments is provided, then the other
|
||||
\emph{must} be provided and \code{choices} \emph{must not} be provided. The
|
||||
advantage of using both of these over a named list for \code{choices} is
|
||||
that \code{choiceNames} allows any type of UI object to be passed through
|
||||
(tag objects, icons, HTML code, ...), instead of just simple text. See
|
||||
Examples.}
|
||||
}
|
||||
\description{
|
||||
Change the value of a radio input on the client
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user