Compare commits

...

127 Commits

Author SHA1 Message Date
Winston Chang
69c32d4d90 Bump version to 1.0.3 2017-04-25 15:33:10 -05:00
Winston Chang
36ffebd975 Workaround for NOTE about objects in yet-unreleased version of ggplot2 2017-04-25 15:33:10 -05:00
Winston Chang
deb56539fb Better reactivePoll example. Closes #1678 2017-04-25 10:48:29 -05:00
Winston Chang
af8d099b9f Don't call body(NULL). Fixes #1676 2017-04-24 13:42:22 -05:00
Winston Chang
eed869d321 Make fileInput progress bar change color on error (#1673)
* Make fileInput progress bar change color on error. Fixes #1672

* Grunt

* Update NEWS
2017-04-21 11:33:14 -05:00
Winston Chang
f8f2acf6c3 Bump version to 1.0.2.9000 2017-04-18 16:38:14 -05:00
Winston Chang
7be9f74827 Merge tag 'v1.0.2'
Shiny 1.0.2 on CRAN
2017-04-18 16:36:44 -05:00
Barbara Borges Ribeiro
ed77982330 Merge pull request #1670 from rstudio/joe/prebuilt
pre-built => prebuilt
2017-04-18 19:58:32 +01:00
Joe Cheng
e1b47eca90 pre-built => prebuilt 2017-04-18 11:09:45 -07:00
Winston Chang
bfa0b2d2bc Bump version to 1.0.2 and update NEWS 2017-04-13 14:13:41 -05:00
Winston Chang
d67783edbd Fix typo 2017-04-13 14:11:15 -05:00
Joe Cheng
77712b6664 Use RStudio replacement for deprecated MathJax CDN (#1664)
* Use RStudio replacement for deprecated MathJax CDN

* Add link to PR
2017-04-12 14:41:21 -05:00
Winston Chang
1633e7faa6 Fix Bootstrap URL. Closes #1662 2017-04-10 10:01:42 -05:00
Joe Cheng
2dc5ee5862 Merge pull request #1661 from rstudio/joe/bugfix/showcase-code-margin
Fix #1654: Empty space below showcase code
2017-04-07 17:39:01 -07:00
Joe Cheng
bbaea23eea Fix #1654: Empty space below showcase code 2017-04-07 17:37:54 -07:00
Barbara Borges Ribeiro
d112ac7eef fix documentation (worng/misleading code example) 2017-04-05 17:58:39 -05:00
Barbara Borges Ribeiro
cf21e987f2 Add shiny:sessionInit event (#1568)
* added a shiny:sessionInit JS event that is triggered at the end of the session's initialize method

* new entry

* update NEWS

* correct version number in NEWS.md

* fix typo
2017-04-05 10:50:42 -05:00
Barbara Borges Ribeiro
dae11765bc allow the choices argument in checkboxGroupInput() to be NULL (#1652)
* allow the `choices` argument in `checkboxGroupInput()` to be `NULL` or `c()` to keep backward compatibility with Shiny < 1.0.1 (fixes #1649)

* use vapply

* added one more test; reimplemented logic for checking if choice args are null
2017-04-05 10:12:44 -05:00
Barbara Borges Ribeiro
df30a3c7f4 PR: Add CC-BY-SA-4.0 license to showcase 2017-04-03 18:24:28 +01:00
Joe Cheng
aaa4600597 Bump version number to 1.0.1.9000 2017-04-03 10:22:08 -07:00
Joe Cheng
ba1730d26b Add CC-BY-SA-4.0 license to showcase 2017-04-03 10:18:04 -07:00
Barbara Borges Ribeiro
d1b5c812f7 re-run grunt to update version number embedded in shiny.min.js 2017-03-31 16:49:11 +01:00
Barbara Borges Ribeiro
5bfe6d1c84 bump version numbers in DESCRIPTION and NEWS 2017-03-31 16:48:13 +01:00
Winston Chang
9804a794fd Merge pull request #1641 from rstudio/fix-plot-rounding
Round brush coordinates
2017-03-31 10:46:44 -05:00
Winston Chang
0344645208 Grunt 2017-03-31 10:42:44 -05:00
Winston Chang
f56ad6e787 Update NEWS 2017-03-31 10:42:43 -05:00
Winston Chang
7492db592b Round brush coordinates to 14 digits. Fixes #1634 2017-03-31 10:42:43 -05:00
Winston Chang
2e80ecf8a7 Rebuild ion.rangeSlider.min.js 2017-03-30 15:48:47 -05:00
Joe Cheng
6993551a44 Fix #1637: Outputs stay faded on MS Edge (#1640) 2017-03-30 20:25:27 +01:00
Joe Cheng
9bff15adfe Fix #1632: Showcase mode comes up almost blank in IE9 & 11 (#1633)
If the width is made very wide in showcase mode with side-by-side
arrangement, the app shrinks to almost nothing. For some reason the
zoom CSS property (which we set using jQuery.animate) is set to
"1%" instead of "1".

Numbers and percentages are equally valid here, and the issue goes
away if we use percentage.
2017-03-29 10:16:17 -05:00
Winston Chang
0c24da2358 Stop propagation of mouse events on slider (#1631)
see (https://github.com/rstudio/shiny/issues/711)
2017-03-28 20:38:46 +01:00
Barbara Borges Ribeiro
4ee4adb43d doc changes 2017-03-27 21:12:46 +01:00
Joe Cheng
e33b028348 Merge pull request #1628 from rstudio/wch-rm-object-assign
Remove babel-polyfill
2017-03-27 13:07:47 -07:00
Winston Chang
384d9c1841 Grunt 2017-03-27 15:02:16 -05:00
Winston Chang
f78fcd6b5f Remove need for babel-polyfill 2017-03-27 15:02:03 -05:00
Barbara Borges Ribeiro
711a72989b Update NEWS.md 2017-03-27 17:53:59 +01:00
Barbara Borges Ribeiro
d62a2fc1d5 Allow arbitrary UI code in the choiceNames for radio buttons and checkbox group input (#1521) 2017-03-27 16:51:44 +01:00
Barbara Borges Ribeiro
f33f712a3a fix typo 2017-03-27 10:00:47 +01:00
Joe Cheng
3315b3310b Merge pull request #1614 from rstudio/joe/feature/reactiveVal
Add reactiveVal() for single reactive value
2017-03-24 13:08:47 -07:00
Joe Cheng
c7134b16ed Add link to PR for reactiveVal feature 2017-03-24 13:08:09 -07:00
Winston Chang
f36f710661 Make sure reactiveTimer gets session at creation time. Fixes #1621 2017-03-24 13:47:25 -05:00
Joe Cheng
00ab8681c7 Merge pull request #1619 from rstudio/wch-fileinput
Make fileInput text customizable. Closes #1617
2017-03-23 17:16:32 -07:00
Winston Chang
4137bbac94 NEWS 2017-03-23 14:33:22 -05:00
Winston Chang
750b2ad599 Make fileInput text customizable. Closes #1617 2017-03-23 14:31:31 -05:00
Joe Cheng
511c833fbb More code review feedback 2017-03-23 10:29:29 -07:00
Joe Cheng
29063a0c07 Code review feedback 2017-03-23 10:24:06 -07:00
Barbara Borges Ribeiro
67909b3557 updated tools/README.md (#1616) 2017-03-23 10:07:41 -05:00
Joe Cheng
102c12d36c Add NEWS item 2017-03-22 16:12:07 -07:00
Joe Cheng
dc51651665 Add S3 generics for format/print; freezeReactiveVal
Also changed the classes of reactive expressions and reactiveVal
from "reactive" and "reactiveVal" to c("reactiveExpr", "reactive")
and c("reactiveVal", "reactive")
2017-03-22 11:29:22 -07:00
Joe Cheng
8b563d6d5f Fix regex for old versions of R 2017-03-22 10:37:16 -07:00
Joe Cheng
eb8b88027e Automatic labelling of reactiveVals 2017-03-22 09:47:08 -07:00
Joe Cheng
a5b7f307ed Add reactiveVal() for single reactive value 2017-03-21 16:38:32 -07:00
Winston Chang
45fca425aa Change NS() to return a vectorized function (#1613)
* Change NS() to return a vectorized function

* Update NEWS

* Use vectorized ns()

* Use correct separator
2017-03-21 15:57:38 -05:00
Winston Chang
a0bd9b5fd7 Redocument with Roxygen 6.0.1 2017-03-21 14:02:29 -05:00
Winston Chang
c12e24e3e3 Properly register bookmark excludes for modules. Fixes #1598 (#1599)
* Scopes: properly register bookmark excludes. Fixes #1598

* Update NEWS
2017-03-21 13:56:08 -05:00
Winston Chang
d147c5a153 Don't use data-drag-interval for non-range sliders. Fixes #1605 (#1610)
* Don't use data-drag-interval for non-range sliders. Fixes #1605

* Update NEWS
2017-03-16 15:49:46 -05:00
Winston Chang
7a833456d9 Use consistent value caching format 2017-03-10 12:03:24 -06:00
Winston Chang
306f33dfc4 Fix value access 2017-03-09 16:16:16 -06:00
Winston Chang
a2745a4060 Grunt 2017-03-03 15:28:47 -06:00
Winston Chang
46b68c7b2a Bump version to 1.0.0.9001 2017-03-03 15:28:47 -06:00
Winston Chang
4264760113 Add binding and el fields to shiny:inputchanged event (#1596)
* Remove unused 'immediate' arguments

* Add opts argument to setInput methods

* Extract input values without opts

* Consistent interface for setting initial values

* Update NEWS

* Add binding and el when fileInputBinding triggers shiny:inputchanged

* Revert "Consistent interface for setting initial values"

This reverts commit 12c0b6e72a.

* Move InputDeferDecorater function

The new placement properly reflects the decorator stack

* Fix indentation

* bindInputs: make sure value is set immediately

* Only use opts where necessary in input decorators

* Properly send initial values

* Move initial value of .clientdata_allowDataUriScheme to better place

* Fix indentation

* Add InputValidateDecorator

* Better variable name

* Add function for default input options

* Simplify code
2017-03-03 15:27:32 -06:00
Winston Chang
42dedbbd9a Simplify user value check 2017-03-02 13:30:47 -06:00
Winston Chang
ea99bfdb16 Update NEWS 2017-02-28 10:48:48 -06:00
Winston Chang
2ccb934338 Merge pull request #1592 from akersting/master
fix: dateRangeInput did not respect weekstart arg
2017-02-28 10:46:35 -06:00
Winston Chang
367027cfbc Merge branch 'wch/redundant-setinput' 2017-02-28 09:23:17 -06:00
Winston Chang
c4ebd3b6d5 Merge pull request #1594 from rstudio/wch/fix-dynamic-input
Make sure input deduplication respects inputType. Closes #162
2017-02-28 09:19:41 -06:00
Winston Chang
5f8cd82a09 Update NEWS 2017-02-24 19:43:55 -06:00
Winston Chang
0ef15fa662 Remove redundant calls to setInput 2017-02-24 19:43:55 -06:00
Winston Chang
c05452af91 Update NEWS 2017-02-24 15:16:12 -06:00
Winston Chang
4c8bafcf9a Make sure input deduplication respects inputType. Closes #162 2017-02-24 15:11:18 -06:00
Andreas Kersting
034f30a49a fix: dateRangeInput did not respect weekstart arg 2017-02-23 07:38:10 +01:00
Winston Chang
0f13075e17 NEWS edits 2017-02-10 14:28:15 -06:00
Winston Chang
ad274a5981 Grunt 2017-02-10 14:26:48 -06:00
Winston Chang
fdbcbaec8a Merge pull request #1579 from albertosantini/fix-1577
Improve escapeHTML
2017-02-10 14:26:22 -06:00
Alberto Santini
9c09072ee6 Update NEWS 2017-02-10 20:16:57 +01:00
Alberto Santini
0a4ca56da9 Improve escapeHTML
Replacing one char after another is not a best practice, due to the order dependency of replacing, xss risk and performance.

Fix #1577
2017-02-10 18:15:07 +01:00
Winston Chang
2b494398f2 Merge pull request #1578 from rstudio/wch/ggplot-api
Add plot interaction support for ggplot2 api
2017-02-10 10:56:59 -06:00
Winston Chang
95585c2264 Update NEWS 2017-02-10 10:42:27 -06:00
Winston Chang
92f9f0da9e Restructure code for clarity 2017-02-09 11:23:13 -06:00
Winston Chang
fe943b5e95 Update plot interaction for ggplot2 > 2.2.1 2017-02-09 11:01:47 -06:00
Winston Chang
3479a4661a Prepare code for ggplot2 api 2017-02-02 11:12:22 -06:00
Winston Chang
7ba438cf7c Add entries to staticdocs index 2017-02-01 11:41:18 -06:00
Winston Chang
c761e9fba0 NEWS formatting fixes 2017-02-01 11:37:31 -06:00
Winston Chang
deae31ea4a Merge pull request #1570 from rstudio/wch/simplify-fileupload
Remove shiny:fileuploaded JS event
2017-02-01 11:34:01 -06:00
Winston Chang
547355a163 Grunt 2017-02-01 11:29:21 -06:00
Winston Chang
9be4cb132c NEWS 2017-02-01 11:29:21 -06:00
Winston Chang
3e25c9f3f4 Remove shiny:fileuploaded event 2017-02-01 11:17:08 -06:00
Barbara Borges Ribeiro
220c7e9139 decode URLs in staticHandler func - fixes #1565 (via #1566) 2017-02-01 06:27:11 +00:00
Winston Chang
79a085a9be Merge pull request #1547 from rstudio/wch/fix-progress
Fix progress bar
2017-01-31 20:38:38 -06:00
Winston Chang
b505c5a9d3 Grunt 2017-01-31 20:38:16 -06:00
Winston Chang
03ba660ea1 Update NEWS 2017-01-31 20:37:39 -06:00
Winston Chang
5aeb361f6d Set starting value to NULL 2017-01-31 20:36:28 -06:00
Winston Chang
0e519a4e97 Progress: store value as raw value instead of normalized 2017-01-31 20:36:28 -06:00
Winston Chang
4feee00d34 NULL value no longer makes progress bar go to 100%. Closes #1472
This also removes the documentation which said that using NULL would cause the
progress bar to be hidden.
2017-01-31 20:36:28 -06:00
Winston Chang
ef5e4cdc0a Merge pull request #1559 from rstudio/wch/download-event
Add shiny:filedownload Javascript event
2017-01-31 20:29:25 -06:00
Winston Chang
67c599f50b Update NEWS 2017-01-31 20:27:20 -06:00
Winston Chang
5af9b61357 Grunt 2017-01-31 20:25:00 -06:00
Winston Chang
1d6771b4ed Unexport markOutputAttrs and add snapshotExclude function 2017-01-31 20:24:52 -06:00
Winston Chang
c55dc0a58e Add ability to exclude outputs from snapshots 2017-01-31 20:23:34 -06:00
Winston Chang
c525d55db8 Add shiny:filedownload JS event 2017-01-31 20:23:34 -06:00
Winston Chang
408f66ef80 Merge pull request #1541 from rstudio/wch/file-input-event
Make fileInput trigger shiny:inputchanged.
2017-01-31 16:29:44 -06:00
Winston Chang
7f73a047a4 Grunt 2017-01-31 16:27:50 -06:00
Winston Chang
015bc98d60 Trigger inputchanged event when fileupload is completed 2017-01-31 16:27:16 -06:00
Winston Chang
5cd9ba609a Make fileInput trigger shiny:inputchanged. Closes #1511 2017-01-31 16:27:16 -06:00
Winston Chang
c8ed6544db Fix documentation link. Closes #1567 2017-01-31 11:40:24 -06:00
Winston Chang
1162113d3b Re-document 2017-01-30 13:47:20 -06:00
Winston Chang
1612503e7b Update selectize URLs. Closes #1564 2017-01-30 13:46:12 -06:00
Joe Cheng
34ba85df3b Merge pull request #1563 from rstudio/barbara/userInfo
Barbara/user info
2017-01-30 09:55:56 -08:00
Barbara Borges Ribeiro
8206e7d2a2 delete old message handler 2017-01-27 20:42:04 +00:00
Barbara Borges Ribeiro
3e29672c70 news item 2017-01-27 20:40:07 +00:00
Barbara Borges Ribeiro
f67aaafe4f some adjustments after feedback 2017-01-27 20:39:18 +00:00
Barbara Borges Ribeiro
ed704afc07 remove console.log and re-grunt 2017-01-27 20:38:17 +00:00
Barbara Borges Ribeiro
bbbfacb4b2 grunt 2017-01-27 20:37:58 +00:00
Barbara Borges Ribeiro
cf16d2e52d listify 2017-01-27 20:37:32 +00:00
Barbara Borges Ribeiro
6268e6e1c9 will be removed!! only for testing purposes 2017-01-27 20:36:37 +00:00
Barbara Borges Ribeiro
99b8e5b303 stuff 2017-01-27 20:36:37 +00:00
Winston Chang
73446af330 Convert tabs to spaces in examples 2017-01-26 15:06:15 -06:00
Barbara Borges Ribeiro
a0b917a207 support pushState for pseudo-nav
see the documentation for details (`?updateQueryString` and `?getQueryString`)
2017-01-25 23:45:26 +00:00
Winston Chang
53ec7edd06 Another typo 2017-01-17 12:02:39 -05:00
Winston Chang
ff804c0ff8 Typo 2017-01-17 12:01:51 -05:00
Winston Chang
9d69ff01b3 Update ion.rangeSlider to 2.1.6 (#1540)
* Update ion.rangeSlider to 2.1.6

* Simplify code when slider separator is ""

* Add links to NEWS
2017-01-17 12:00:47 -05:00
Winston Chang
61831f530f Merge pull request #1525 from rstudio/wch/ggplot-coord-fixed
Add plot interaction support for coord_fixed
2017-01-16 13:01:01 -05:00
Winston Chang
6065db1d24 Update NEWS 2017-01-16 13:00:41 -05:00
Winston Chang
270b8415e8 Add plot interaction support for coord_fixed. Closes #1121 2017-01-16 12:58:58 -05:00
Winston Chang
1987331a70 Bump version to 1.0.0.9000 2017-01-16 12:55:36 -05:00
Winston Chang
ab85216b96 Merge tag 'v1.0.0' 2017-01-13 13:36:23 -05:00
Winston Chang
b5cb78c77e Update URL 2017-01-10 10:13:06 -06:00
213 changed files with 2793 additions and 968 deletions

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 1.0.0
Version: 1.0.3
Authors@R: c(
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
@@ -56,7 +56,7 @@ 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:
@@ -98,6 +98,9 @@ Collate:
'diagnose.R'
'fileupload.R'
'graph.R'
'reactives.R'
'reactive-domains.R'
'history.R'
'hooks.R'
'html-deps.R'
'htmltools.R'
@@ -129,8 +132,6 @@ Collate:
'priorityqueue.R'
'progress.R'
'react.R'
'reactive-domains.R'
'reactives.R'
'render-plot.R'
'render-table.R'
'run-url.R'
@@ -146,4 +147,4 @@ Collate:
'test-export.R'
'timer.R'
'update-input.R'
RoxygenNote: 5.0.1
RoxygenNote: 6.0.1

View File

@@ -12,7 +12,7 @@ these components are included below):
- Respond.js, https://github.com/scottjehl/Respond
- bootstrap-datepicker, https://github.com/eternicode/bootstrap-datepicker
- Font Awesome, https://github.com/FortAwesome/Font-Awesome
- selectize.js, https://github.com/brianreavis/selectize.js
- selectize.js, https://github.com/selectize/selectize.js
- es5-shim, https://github.com/es-shims/es5-shim
- ion.rangeSlider, https://github.com/IonDen/ion.rangeSlider
- strftime for Javascript, https://github.com/samsonjs/strftime

View File

@@ -22,6 +22,8 @@ S3method(as.shiny.appobj,list)
S3method(as.shiny.appobj,shiny.appobj)
S3method(as.tags,shiny.appobj)
S3method(as.tags,shiny.render.function)
S3method(format,reactiveExpr)
S3method(format,reactiveVal)
S3method(names,reactivevalues)
S3method(print,reactive)
S3method(print,shiny.appobj)
@@ -84,9 +86,12 @@ export(flowLayout)
export(fluidPage)
export(fluidRow)
export(formatStackTrace)
export(freezeReactiveVal)
export(freezeReactiveValue)
export(getDefaultReactiveDomain)
export(getQueryString)
export(getShinyOption)
export(getUrlHash)
export(h1)
export(h2)
export(h3)
@@ -168,6 +173,7 @@ export(reactiveTable)
export(reactiveText)
export(reactiveTimer)
export(reactiveUI)
export(reactiveVal)
export(reactiveValues)
export(reactiveValuesToList)
export(registerInputHandler)
@@ -211,6 +217,7 @@ export(sidebarLayout)
export(sidebarPanel)
export(singleton)
export(sliderInput)
export(snapshotExclude)
export(span)
export(splitLayout)
export(stopApp)

107
NEWS.md
View File

@@ -1,3 +1,108 @@
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
================
This is a hotfix release of Shiny. The primary reason for this release is because the web host for MathJax JavaScript library is scheduled to be shut down in the next few weeks. After it is shut down, Shiny applications that use MathJax will no longer be able to load the MathJax library if they are run with Shiny 1.0.1 and below. (If you don't know whether your application uses MathJax, it probably does not.) For more information about why the MathJax CDN is shutting down, see https://www.mathjax.org/cdn-shutting-down/.
## Full changelog
### Minor new features and improvements
* Added a `shiny:sessioninitialized` Javascript event, which is fired at the end of the initialize method of the Session object. This allows us to listen for this event when we want to get the value of things like `Shiny.user`. ([#1568](https://github.com/rstudio/shiny/pull/1568))
* Fixed [#1649](https://github.com/rstudio/shiny/issues/1649): allow the `choices` argument in `checkboxGroupInput()` to be `NULL` (or `c()`) to keep backward compatibility with Shiny < 1.0.1. This will result in the same thing as providing `choices = character(0)`. ([#1652](https://github.com/rstudio/shiny/pull/1652))
* The official URL for accessing MathJax libraries over CDN has been deprecated and will be removed soon. We have switched to a new rstudio.com URL that we will support going forward. ([#1664](https://github.com/rstudio/shiny/pull/1664))
### Bug fixes
* Fixed [#1653](https://github.com/rstudio/shiny/issues/1653): wrong code example in documentation. ([#1658](https://github.com/rstudio/shiny/pull/1658))
shiny 1.0.1
================
This is a maintenance release of Shiny, mostly aimed at fixing bugs and introducing minor features. The most notable additions in this version of Shiny are the introduction of the `reactiveVal()` function (it's like `reactiveValues()`, but it only stores a single value), and that the choices of `radioButtons()` and `checkboxGroupInput()` can now contain HTML content instead of just plain text.
## Full changelog
### Breaking changes
* The functions `radioButtons()`, `checkboxGroupInput()` and `selectInput()` (and the corresponding `updateXXX()` functions) no longer accept a `selected` argument whose value is the name of a choice, instead of the value of the choice. This feature had been deprecated since Shiny 0.10 (it printed a warning message, but still tried to match the name to the right choice) and it's now completely unsupported.
### New features
* Added `reactiveVal` function, for storing a single value which can be (reactively) read and written. Similar to `reactiveValues`, except that `reactiveVal` just lets you store a single value instead of storing multiple values by name. ([#1614](https://github.com/rstudio/shiny/pull/1614))
### Minor new features and improvements
* Fixed [#1637](https://github.com/rstudio/shiny/issues/1637): Outputs stay faded on MS Edge. ([#1640](https://github.com/rstudio/shiny/pull/1640))
* Addressed [#1348](https://github.com/rstudio/shiny/issues/1348) and [#1437](https://github.com/rstudio/shiny/issues/1437) by adding two new arguments to `radioButtons()` and `checkboxGroupInput()`: `choiceNames` (list or vector) and `choiceValues` (list or vector). These can be passed in as an alternative to `choices`, with the added benefit that the elements in `choiceNames` can be arbitrary UI (i.e. anything created by `HTML()` and the `tags()` functions, like icons and images). While the underlying values for each choice (passed in through `choiceValues`) must still be simple text, their visual representation on the app (what the user actually clicks to select a different option) can be any valid HTML element. See `?radioButtons` for a small example. ([#1521](https://github.com/rstudio/shiny/pull/1521))
* Updated `tools/README.md` with more detailed instructions. ([#1616](https://github.com/rstudio/shiny/pull/1616))
* Fixed [#1565](https://github.com/rstudio/shiny/issues/1565), which meant that resources with spaces in their names return HTTP 404. ([#1566](https://github.com/rstudio/shiny/pull/1566))
* Exported `session$user` (if it exists) to the client-side; it's accessible in the Shiny object: `Shiny.user`. ([#1563](https://github.com/rstudio/shiny/pull/1563))
* Added support for HTML5's `pushState` which allows for pseudo-navigation
in shiny apps. For more info, see the documentation (`?updateQueryString` and `?getQueryString`). ([#1447](https://github.com/rstudio/shiny/pull/1447))
* Fixed [#1121](https://github.com/rstudio/shiny/issues/1121): plot interactions with ggplot2 now support `coord_fixed()`. ([#1525](https://github.com/rstudio/shiny/pull/1525))
* Added `snapshotExclude` function, which marks an output so that it is not recorded in a test snapshot. ([#1559](https://github.com/rstudio/shiny/pull/1559))
* Added `shiny:filedownload` JavaScript event, which is triggered when a `downloadButton` or `downloadLink` is clicked. Also, the values of `downloadHandler`s are not recorded in test snapshots, because the values change every time the application is run. ([#1559](https://github.com/rstudio/shiny/pull/1559))
* Added support for plot interactions with ggplot2 > 2.2.1. ([#1578](https://github.com/rstudio/shiny/pull/1578))
* Fixed [#1577](https://github.com/rstudio/shiny/issues/1577): Improved `escapeHTML` (util.js) in terms of the order dependency of replacing, XSS risk attack and performance. ([#1579](https://github.com/rstudio/shiny/pull/1579))
* The `shiny:inputchanged` JavaScript event now includes two new fields, `binding` and `el`, which contain the input binding and DOM element, respectively. Additionally, `Shiny.onInputChange()` now accepts an optional argument, `opts`, which can contain the same fields. ([#1596](https://github.com/rstudio/shiny/pull/1596))
* The `NS()` function now returns a vectorized function. ([#1613](https://github.com/rstudio/shiny/pull/1613))
* Fixed [#1617](https://github.com/rstudio/shiny/issues/1617): `fileInput` can have customized text for the button and the placeholder. ([#1619](https://github.com/rstudio/shiny/pull/1619))
### Bug fixes
* Fixed [#1511](https://github.com/rstudio/shiny/issues/1511): `fileInput`s did not trigger the `shiny:inputchanged` event on the client. Also removed `shiny:fileuploaded` JavaScript event, because it is no longer needed after this fix. ([#1541](https://github.com/rstudio/shiny/pull/1541), [#1570](https://github.com/rstudio/shiny/pull/1570))
* Fixed [#1472](https://github.com/rstudio/shiny/issues/1472): With a Progress object, calling `set(value=NULL)` made the progress bar go to 100%. Now it does not change the value of the progress bar. The documentation also incorrectly said that setting the `value` to `NULL` would hide the progress bar. ([#1547](https://github.com/rstudio/shiny/pull/1547))
* Fixed [#162](https://github.com/rstudio/shiny/issues/162): When a dynamically-generated input changed to a different `inputType`, it might be incorrectly deduplicated. ([#1594](https://github.com/rstudio/shiny/pull/1594))
* Removed redundant call to `inputs.setInput`. ([#1595](https://github.com/rstudio/shiny/pull/1595))
* Fixed bug where `dateRangeInput` did not respect `weekstart` argument. ([#1592](https://github.com/rstudio/shiny/pull/1592))
* Fixed [#1598](https://github.com/rstudio/shiny/issues/1598): `setBookmarkExclude()` did not work properly inside of modules. ([#1599](https://github.com/rstudio/shiny/pull/1599))
* Fixed [#1605](https://github.com/rstudio/shiny/issues/1605): sliders did not move when clicked on the bar area. ([#1610](https://github.com/rstudio/shiny/pull/1610))
* Fixed [#1621](https://github.com/rstudio/shiny/issues/1621): if a `reactiveTimer`'s session was closed before the first time that the `reactiveTimer` fired, then the `reactiveTimer` would not get cleared and would keep firing indefinitely. ([#1623](https://github.com/rstudio/shiny/pull/1623))
* Fixed [#1634](https://github.com/rstudio/shiny/issues/1634): If brushing on a plot causes the plot to redraw, then the redraw could in turn trigger the plot to redraw again and again. This was due to spurious changes in values of floating point numbers. ([#1641](https://github.com/rstudio/shiny/pull/1641))
### Library updates
* Closed [#1500](https://github.com/rstudio/shiny/issues/1500): Updated ion.rangeSlider to 2.1.6. ([#1540](https://github.com/rstudio/shiny/pull/1540))
shiny 1.0.0
===========
@@ -7,7 +112,7 @@ Here are some highlights from this release. For more details, see the full chang
## Support for testing Shiny applications
Shiny now supports automated testing of applications, with the [shinytest](https://github.com/MangoTheCat/shinytest) package. Shinytest has not yet been released on CRAN, but will be soon. ([#18](https://github.com/rstudio/shiny/issues/18), [#1464](https://github.com/rstudio/shiny/pull/1464))
Shiny now supports automated testing of applications, with the [shinytest](https://github.com/rstudio/shinytest) package. Shinytest has not yet been released on CRAN, but will be soon. ([#18](https://github.com/rstudio/shiny/issues/18), [#1464](https://github.com/rstudio/shiny/pull/1464))
## Debounce/throttle reactives

View File

@@ -493,12 +493,90 @@ restoreInput <- function(id, default) {
#' It typically is called from an observer. Note that this will not work in
#' Internet Explorer 9 and below.
#'
#' For \code{mode = "push"}, only three updates are currently allowed:
#' \enumerate{
#' \item the query string (format: \code{?param1=val1&param2=val2})
#' \item the hash (format: \code{#hash})
#' \item both the query string and the hash
#' (format: \code{?param1=val1&param2=val2#hash})
#' }
#'
#' In other words, if \code{mode = "push"}, the \code{queryString} must start
#' with either \code{?} or with \code{#}.
#'
#' A technical curiosity: under the hood, this function is calling the HTML5
#' history API (which is where the names for the \code{mode} argument come from).
#' When \code{mode = "replace"}, the function called is
#' \code{window.history.replaceState(null, null, queryString)}.
#' When \code{mode = "push"}, the function called is
#' \code{window.history.pushState(null, null, queryString)}.
#'
#' @param queryString The new query string to show in the location bar.
#' @param mode When the query string is updated, should the the current history
#' entry be replaced (default), or should a new history entry be pushed onto
#' the history stack? The former should only be used in a live bookmarking
#' context. The latter is useful if you want to navigate between states using
#' the browser's back and forward buttons. See Examples.
#' @param session A Shiny session object.
#' @seealso \code{\link{enableBookmarking}} for examples.
#' @seealso \code{\link{enableBookmarking}}, \code{\link{getQueryString}}
#' @examples
#' ## Only run these examples in interactive sessions
#' if (interactive()) {
#'
#' ## App 1: Doing "live" bookmarking
#' ## Update the browser's location bar every time an input changes.
#' ## This should not be used with enableBookmarking("server"),
#' ## because that would create a new saved state on disk every time
#' ## the user changes an input.
#' enableBookmarking("url")
#' shinyApp(
#' ui = function(req) {
#' fluidPage(
#' textInput("txt", "Text"),
#' checkboxInput("chk", "Checkbox")
#' )
#' },
#' server = function(input, output, session) {
#' observe({
#' # Trigger this observer every time an input changes
#' reactiveValuesToList(input)
#' session$doBookmark()
#' })
#' onBookmarked(function(url) {
#' updateQueryString(url)
#' })
#' }
#' )
#'
#' ## App 2: Printing the value of the query string
#' ## (Use the back and forward buttons to see how the browser
#' ## keeps a record of each state)
#' shinyApp(
#' ui = fluidPage(
#' textInput("txt", "Enter new query string"),
#' helpText("Format: ?param1=val1&param2=val2"),
#' actionButton("go", "Update"),
#' hr(),
#' verbatimTextOutput("query")
#' ),
#' server = function(input, output, session) {
#' observeEvent(input$go, {
#' updateQueryString(input$txt, mode = "push")
#' })
#' output$query <- renderText({
#' query <- getQueryString()
#' queryText <- paste(names(query), query,
#' sep = "=", collapse=", ")
#' paste("Your query string is:\n", queryText)
#' })
#' }
#' )
#' }
#' @export
updateQueryString <- function(queryString, session = getDefaultReactiveDomain()) {
session$updateQueryString(queryString)
updateQueryString <- function(queryString, mode = c("replace", "push"),
session = getDefaultReactiveDomain()) {
mode <- match.arg(mode)
session$updateQueryString(queryString, mode)
}
#' Create a button for bookmarking/sharing

View File

@@ -1453,7 +1453,7 @@ uiOutput <- htmlOutput
#' }
#'
#' @aliases downloadLink
#' @seealso downloadHandler
#' @seealso \code{\link{downloadHandler}}
#' @export
downloadButton <- function(outputId,
label="Download",

95
R/history.R Normal file
View File

@@ -0,0 +1,95 @@
#' @include reactive-domains.R
NULL
#' @include reactives.R
NULL
#' Get the query string / hash component from the URL
#'
#' Two user friendly wrappers for getting the query string and the hash
#' component from the app's URL.
#'
#' These can be particularly useful if you want to display different content
#' depending on the values in the query string / hash (e.g. instead of basing
#' the conditional on an input or a calculated reactive, you can base it on the
#' query string). However, note that, if you're changing the query string / hash
#' programatically from within the server code, you must use
#' \code{updateQueryString(_yourNewQueryString_, mode = "push")}. The default
#' \code{mode} for \code{updateQueryString} is \code{"replace"}, which doesn't
#' raise any events, so any observers or reactives that depend on it will
#' \emph{not} get triggered. However, if you're changing the query string / hash
#' directly by typing directly in the browser and hitting enter, you don't have
#' to worry about this.
#'
#' @param session A Shiny session object.
#'
#' @return For \code{getQueryString}, a named list. For example, the query
#' string \code{?param1=value1&param2=value2} becomes \code{list(param1 =
#' value1, param2 = value2)}. For \code{getUrlHash}, a character vector with
#' the hash (including the leading \code{#} symbol).
#'
#' @seealso \code{\link{updateQueryString}}
#'
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#'
#' ## App 1: getQueryString
#' ## Printing the value of the query string
#' ## (Use the back and forward buttons to see how the browser
#' ## keeps a record of each state)
#' shinyApp(
#' ui = fluidPage(
#' textInput("txt", "Enter new query string"),
#' helpText("Format: ?param1=val1&param2=val2"),
#' actionButton("go", "Update"),
#' hr(),
#' verbatimTextOutput("query")
#' ),
#' server = function(input, output, session) {
#' observeEvent(input$go, {
#' updateQueryString(input$txt, mode = "push")
#' })
#' output$query <- renderText({
#' query <- getQueryString()
#' queryText <- paste(names(query), query,
#' sep = "=", collapse=", ")
#' paste("Your query string is:\n", queryText)
#' })
#' }
#' )
#'
#' ## App 2: getUrlHash
#' ## Printing the value of the URL hash
#' ## (Use the back and forward buttons to see how the browser
#' ## keeps a record of each state)
#' shinyApp(
#' ui = fluidPage(
#' textInput("txt", "Enter new hash"),
#' helpText("Format: #hash"),
#' actionButton("go", "Update"),
#' hr(),
#' verbatimTextOutput("hash")
#' ),
#' server = function(input, output, session) {
#' observeEvent(input$go, {
#' updateQueryString(input$txt, mode = "push")
#' })
#' output$hash <- renderText({
#' hash <- getUrlHash()
#' paste("Your hash is:\n", hash)
#' })
#' }
#' )
#' }
#' @export
getQueryString <- function(session = getDefaultReactiveDomain()) {
parseQueryString(session$clientData$url_search)
}
#' @rdname getQueryString
#' @export
getUrlHash <- function(session = getDefaultReactiveDomain()) {
session$clientData$url_hash
}

View File

@@ -6,9 +6,21 @@
#'
#' @inheritParams textInput
#' @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.
#' 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 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,
#' 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.
#'
#' @return A list of HTML elements that can be added to a UI definition.
#'
#' @family input elements
@@ -26,26 +38,52 @@
#' tableOutput("data")
#' )
#'
#' server <- function(input, output) {
#' server <- function(input, output, session) {
#' output$data <- renderTable({
#' mtcars[, c("mpg", input$variable), drop = FALSE]
#' }, rownames = TRUE)
#' }
#'
#' shinyApp(ui, server)
#'
#' ui <- fluidPage(
#' checkboxGroupInput("icons", "Choose icons:",
#' choiceNames =
#' list(icon("calendar"), icon("bed"),
#' icon("cog"), icon("bug")),
#' choiceValues =
#' list("calendar", "bed", "cog", "bug")
#' ),
#' textOutput("txt")
#' )
#'
#' server <- function(input, output, session) {
#' output$txt <- renderText({
#' icons <- paste(input$icons, collapse = ", ")
#' paste("You chose", icons)
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
checkboxGroupInput <- function(inputId, label, choices, selected = NULL,
inline = FALSE, width = NULL) {
checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL,
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) {
# keep backward compatibility with Shiny < 1.0.1 (see #1649)
if (is.null(choices) && is.null(choiceNames) && is.null(choiceValues)) {
choices <- character(0)
}
args <- normalizeChoicesArgs(choices, choiceNames, choiceValues)
selected <- restoreInput(id = inputId, default = selected)
# resolve names
choices <- choicesWithNames(choices)
if (!is.null(selected))
selected <- validateSelected(selected, choices, inputId)
# default value if it's not specified
if (!is.null(selected)) selected <- as.character(selected)
options <- generateOptions(inputId, choices, selected, inline)
options <- generateOptions(inputId, selected, inline,
'checkbox', args$choiceNames, args$choiceValues)
divClass <- "form-group shiny-input-checkboxgroup shiny-input-container"
if (inline)

View File

@@ -98,7 +98,7 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
class = "input-sm form-control",
type = "text",
`data-date-language` = language,
`data-date-weekstart` = weekstart,
`data-date-week-start` = weekstart,
`data-date-format` = format,
`data-date-start-view` = startview,
`data-min-date` = min,
@@ -110,7 +110,7 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
class = "input-sm form-control",
type = "text",
`data-date-language` = language,
`data-date-weekstart` = weekstart,
`data-date-week-start` = weekstart,
`data-date-format` = format,
`data-date-start-view` = startview,
`data-min-date` = min,

View File

@@ -27,6 +27,9 @@
#' Internet Explorer 9 and earlier.}
#' @param accept A character vector of MIME types; gives the browser a hint of
#' what kind of files the server is expecting.
#' @param buttonLabel The label used on the button. Can be text or an HTML tag
#' object.
#' @param placeholder The text to show before a file has been uploaded.
#'
#' @examples
#' ## Only run examples in interactive R sessions
@@ -70,7 +73,7 @@
#' }
#' @export
fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
width = NULL) {
width = NULL, buttonLabel = "Browse...", placeholder = "No file selected") {
restoredValue <- restoreInput(id = inputId, default = NULL)
@@ -105,12 +108,12 @@ fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
div(class = "input-group",
tags$label(class = "input-group-btn",
span(class = "btn btn-default btn-file",
"Browse...",
buttonLabel,
inputTag
)
),
tags$input(type = "text", class = "form-control",
placeholder = "No file selected", readonly = "readonly"
placeholder = placeholder, readonly = "readonly"
)
),

View File

@@ -11,11 +11,22 @@
#'
#' @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)
#' 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)
#' 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.
#'
#' @family input elements
#' @seealso \code{\link{updateRadioButtons}}
@@ -47,27 +58,46 @@
#' }
#'
#' shinyApp(ui, server)
#'
#' ui <- fluidPage(
#' radioButtons("rb", "Choose one:",
#' choiceNames = list(
#' icon("calendar"),
#' HTML("<p style='color:red;'>Red Text</p>"),
#' "Normal text"
#' ),
#' choiceValues = list(
#' "icon", "html", "text"
#' )),
#' textOutput("txt")
#' )
#'
#' server <- function(input, output) {
#' output$txt <- renderText({
#' paste("You chose", input$rb)
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#' @export
radioButtons <- function(inputId, label, choices, selected = NULL,
inline = FALSE, width = NULL) {
radioButtons <- function(inputId, label, choices = NULL, selected = NULL,
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) {
# resolve names
choices <- choicesWithNames(choices)
args <- normalizeChoicesArgs(choices, choiceNames, choiceValues)
selected <- restoreInput(id = inputId, default = selected)
# default value if it's not specified
selected <- if (is.null(selected)) choices[[1]] else {
validateSelected(selected, choices, inputId)
}
selected <- if (is.null(selected)) args$choiceValues[[1]] else as.character(selected)
if (length(selected) > 1) stop("The 'selected' argument must be of length 1")
options <- generateOptions(inputId, choices, selected, inline, type = 'radio')
options <- generateOptions(inputId, selected, inline,
'radio', args$choiceNames, args$choiceValues)
divClass <- "form-group shiny-input-radiogroup shiny-input-container"
if (inline)
divClass <- paste(divClass, "shiny-input-container-inline")
if (inline) divClass <- paste(divClass, "shiny-input-container-inline")
tags$div(id = inputId,
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),

View File

@@ -5,7 +5,7 @@
#'
#' By default, \code{selectInput()} and \code{selectizeInput()} use the
#' JavaScript library \pkg{selectize.js}
#' (\url{https://github.com/brianreavis/selectize.js}) to instead of the basic
#' (\url{https://github.com/selectize/selectize.js}) to instead of the basic
#' select input element. To use the standard HTML select input element, use
#' \code{selectInput()} with \code{selectize=FALSE}.
#'
@@ -74,8 +74,8 @@
#' }
#' @export
selectInput <- function(inputId, label, choices, selected = NULL,
multiple = FALSE, selectize = TRUE, width = NULL,
size = NULL) {
multiple = FALSE, selectize = TRUE, width = NULL,
size = NULL) {
selected <- restoreInput(id = inputId, default = selected)
@@ -85,7 +85,7 @@ selectInput <- function(inputId, label, choices, selected = NULL,
# default value if it's not specified
if (is.null(selected)) {
if (!multiple) selected <- firstChoice(choices)
} else selected <- validateSelected(selected, choices, inputId)
} else selected <- as.character(selected)
if (!is.null(size) && selectize) {
stop("'size' argument is incompatible with 'selectize=TRUE'.")

View File

@@ -164,23 +164,21 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
`data-grid` = ticks,
`data-grid-num` = n_ticks,
`data-grid-snap` = FALSE,
`data-prettify-separator` = sep,
`data-prettify-enabled` = (sep != ""),
`data-prefix` = pre,
`data-postfix` = post,
`data-keyboard` = TRUE,
`data-keyboard-step` = step / (max - min) * 100,
`data-drag-interval` = dragRange,
# This value is only relevant for range sliders; for non-range sliders it
# causes problems since ion.RangeSlider 2.1.2 (issue #1605).
`data-drag-interval` = if (length(value) > 1) dragRange,
# The following are ignored by the ion.rangeSlider, but are used by Shiny.
`data-data-type` = dataType,
`data-time-format` = timeFormat,
`data-timezone` = timezone
))
if (sep == "") {
sliderProps$`data-prettify-enabled` <- "0"
} else {
sliderProps$`data-prettify-separator` <- sep
}
# Replace any TRUE and FALSE with "true" and "false"
sliderProps <- lapply(sliderProps, function(x) {
if (identical(x, TRUE)) "true"
@@ -220,7 +218,7 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
}
dep <- list(
htmlDependency("ionrangeslider", "2.1.2", c(href="shared/ionrangeslider"),
htmlDependency("ionrangeslider", "2.1.6", c(href="shared/ionrangeslider"),
script = "js/ion.rangeSlider.min.js",
# ion.rangeSlider also needs normalize.css, which is already included in
# Bootstrap.

View File

@@ -2,45 +2,62 @@ controlLabel <- function(controlName, label) {
label %AND% tags$label(class = "control-label", `for` = controlName, label)
}
# Before shiny 0.9, `selected` refers to names/labels of `choices`; now it
# refers to values. Below is a function for backward compatibility. It also
# coerces the value to `character`.
validateSelected <- function(selected, choices, inputId) {
# this line accomplishes two tings:
# - coerces selected to character
# - drops name, otherwise toJSON() keeps it too
selected <- as.character(selected)
# if you are using optgroups, you're using shiny > 0.10.0, and you should
# already know that `selected` must be a value instead of a label
if (needOptgroup(choices)) return(selected)
if (is.list(choices)) choices <- unlist(choices)
nms <- names(choices)
# labels and values are identical, no need to validate
if (identical(nms, unname(choices))) return(selected)
# when selected labels instead of values
i <- (selected %in% nms) & !(selected %in% choices)
if (any(i)) {
warnFun <- if (all(i)) {
# replace names with values
selected <- unname(choices[selected])
warning
} else stop # stop when it is ambiguous (some labels == values)
warnFun("'selected' must be the values instead of names of 'choices' ",
"for the input '", inputId, "'")
# This function takes in either a list or vector for `choices` (and
# `choiceNames` and `choiceValues` are passed in as NULL) OR it takes
# in a list or vector for both `choiceNames` and `choiceValues` (and
# `choices` is passed as NULL) and returns a list of two elements:
# - `choiceNames` is a vector or list that holds the options names
# (each element can be arbitrary UI, or simple text)
# - `choiceValues` is a vector or list that holds the options values
# (each element must be simple text)
normalizeChoicesArgs <- function(choices, choiceNames, choiceValues,
mustExist = TRUE) {
# if-else to check that either choices OR (choiceNames + choiceValues)
# were correctly provided
if (is.null(choices)) {
if (is.null(choiceNames) || is.null(choiceValues)) {
if (mustExist) {
stop("Please specify a non-empty vector for `choices` (or, ",
"alternatively, for both `choiceNames` AND `choiceValues`).")
} else {
if (is.null(choiceNames) && is.null(choiceValues)) {
# this is useful when we call this function from `updateInputOptions()`
# in which case, all three `choices`, `choiceNames` and `choiceValues`
# may legitimately be NULL
return(list(choiceNames = NULL, choiceValues = NULL))
} else {
stop("One of `choiceNames` or `choiceValues` was set to ",
"NULL, but either both or none should be NULL.")
}
}
}
if (length(choiceNames) != length(choiceValues)) {
stop("`choiceNames` and `choiceValues` must have the same length.")
}
if (anyNamed(choiceNames) || anyNamed(choiceValues)) {
stop("`choiceNames` and `choiceValues` must not be named.")
}
} else {
if (!is.null(choiceNames) || !is.null(choiceValues)) {
warning("Using `choices` argument; ignoring `choiceNames` and `choiceValues`.")
}
choices <- choicesWithNames(choices) # resolve names if not specified
choiceNames <- names(choices)
choiceValues <- unname(choices)
}
selected
}
return(list(choiceNames = as.list(choiceNames),
choiceValues = as.list(as.character(choiceValues))))
}
# generate options for radio buttons and checkbox groups (type = 'checkbox' or
# 'radio')
generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox') {
generateOptions <- function(inputId, selected, inline, type = 'checkbox',
choiceNames, choiceValues,
session = getDefaultReactiveDomain()) {
# generate a list of <input type=? [checked] />
options <- mapply(
choices, names(choices),
choiceValues, choiceNames,
FUN = function(value, name) {
inputTag <- tags$input(
type = type, name = inputId, value = value
@@ -48,14 +65,18 @@ generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox
if (value %in% selected)
inputTag$attribs$checked <- "checked"
# in case, the options include UI code other than text
# (arbitrary HTML using the tags() function or equivalent)
pd <- processDeps(name, session)
# If inline, there's no wrapper div, and the label needs a class like
# checkbox-inline.
if (inline) {
tags$label(class = paste0(type, "-inline"), inputTag, tags$span(name))
tags$label(class = paste0(type, "-inline"), inputTag,
tags$span(pd$html, pd$dep))
} else {
tags$div(class = type,
tags$label(inputTag, tags$span(name))
)
tags$div(class = type, tags$label(inputTag,
tags$span(pd$html, pd$dep)))
}
},
SIMPLIFY = FALSE, USE.NAMES = FALSE

View File

@@ -191,7 +191,7 @@ staticHandler <- function(root) {
if (!identical(req$REQUEST_METHOD, 'GET'))
return(NULL)
path <- req$PATH_INFO
path <- URLdecode(req$PATH_INFO)
if (is.null(path))
return(httpResponse(400, content="<h1>Bad Request</h1>"))

View File

@@ -55,7 +55,6 @@
#' de-emphasized appearance relative to \code{message}.
#' @param value A numeric value at which to set
#' the progress bar, relative to \code{min} and \code{max}.
#' \code{NULL} hides the progress bar, if it is currently visible.
#' @param style Progress display style. If \code{"notification"} (the default),
#' the progress indicator will show using Shiny's notification API. If
#' \code{"old"}, use the same HTML and CSS used in Shiny 0.13.2 and below
@@ -98,7 +97,6 @@
#' @export
Progress <- R6Class(
'Progress',
portable = TRUE,
public = list(
initialize = function(session = getDefaultReactiveDomain(),
@@ -112,8 +110,8 @@ Progress <- R6Class(
private$id <- createUniqueId(8)
private$min <- min
private$max <- max
private$style <- match.arg(style, choices = c("notification", "old"))
private$value <- NULL
private$style <- match.arg(style, choices = c("notification", "old"))
private$closed <- FALSE
session$sendProgress('open', list(id = private$id, style = private$style))
@@ -125,15 +123,15 @@ Progress <- R6Class(
return()
}
if (is.null(value) || is.na(value)) {
if (is.null(value) || is.na(value))
value <- NULL
} else {
if (!is.null(value)) {
private$value <- value
# Normalize value to number between 0 and 1
value <- min(1, max(0, (value - private$min) / (private$max - private$min)))
}
private$value <- value
data <- dropNulls(list(
id = private$id,
message = message,
@@ -142,11 +140,14 @@ Progress <- R6Class(
style = private$style
))
private$session$sendProgress('update', data)
private$session$sendProgress('update', data)
},
inc = function(amount = 0.1, message = NULL, detail = NULL) {
value <- min(self$getValue() + amount, private$max)
if (is.null(private$value))
private$value <- private$min
value <- min(private$value + amount, private$max)
self$set(value, message, detail)
},
@@ -154,10 +155,7 @@ Progress <- R6Class(
getMax = function() private$max,
# Return value (not the normalized 0-1 value, but in the original range)
getValue = function() {
private$value * (private$max - private$min) + private$min
},
getValue = function() private$value,
close = function() {
if (private$closed) {
@@ -173,12 +171,12 @@ Progress <- R6Class(
),
private = list(
session = 'environment',
session = 'ShinySession',
id = character(0),
min = numeric(0),
max = numeric(0),
style = character(0),
value = NULL,
value = numeric(0),
closed = logical(0)
)
)
@@ -239,8 +237,7 @@ Progress <- R6Class(
#' \code{"old"}, use the same HTML and CSS used in Shiny 0.13.2 and below
#' (this is for backward-compatibility).
#' @param value Single-element numeric vector; the value at which to set the
#' progress bar, relative to \code{min} and \code{max}. \code{NULL} hides the
#' progress bar, if it is currently visible.
#' progress bar, relative to \code{min} and \code{max}.
#'
#' @examples
#' ## Only run examples in interactive R sessions

View File

@@ -38,6 +38,228 @@ Dependents <- R6Class(
)
# ReactiveVal ---------------------------------------------------------------
ReactiveVal <- R6Class(
'ReactiveVal',
portable = FALSE,
private = list(
value = NULL,
label = NULL,
frozen = FALSE,
dependents = Dependents$new()
),
public = list(
initialize = function(value, label = NULL) {
private$value <- value
private$label <- label
.graphValueChange(private$label, value)
},
get = function() {
private$dependents$register(depLabel = private$label)
if (private$frozen)
reactiveStop()
private$value
},
set = function(value) {
if (identical(private$value, value)) {
return(invisible(FALSE))
}
private$value <- value
.graphValueChange(private$label, value)
private$dependents$invalidate()
invisible(TRUE)
},
freeze = function(session = getDefaultReactiveDomain()) {
if (is.null(session)) {
stop("Can't freeze a reactiveVal without a reactive domain")
}
session$onFlushed(function() {
self$thaw()
})
private$frozen <- TRUE
},
thaw = function() {
private$frozen <- FALSE
},
isFrozen = function() {
private$frozen
},
format = function(...) {
# capture.output(print()) is necessary because format() doesn't
# necessarily return a character vector, e.g. data.frame.
label <- capture.output(print(base::format(private$value, ...)))
if (length(label) == 1) {
paste0("reactiveVal: ", label)
} else {
c("reactiveVal:", label)
}
}
)
)
#' Create a (single) reactive value
#'
#' The \code{reactiveVal} function is used to construct a "reactive value"
#' object. This is an object used for reading and writing a value, like a
#' variable, but with special capabilities for reactive programming. When you
#' read the value out of a reactiveVal object, the calling reactive expression
#' takes a dependency, and when you change the value, it notifies any reactives
#' that previously depended on that value.
#'
#' \code{reactiveVal} is very similar to \code{\link{reactiveValues}}, except
#' that the former is for a single reactive value (like a variable), whereas the
#' latter lets you conveniently use multiple reactive values by name (like a
#' named list of variables). For a one-off reactive value, it's more natural to
#' use \code{reactiveVal}. See the Examples section for an illustration.
#'
#' @param value An optional initial value.
#' @param label An optional label, for debugging purposes (see
#' \code{\link{showReactLog}}). If missing, a label will be automatically
#' created.
#'
#' @return A function. Call the function with no arguments to (reactively) read
#' the value; call the function with a single argument to set the value.
#'
#' @examples
#'
#' \dontrun{
#'
#' # Create the object by calling reactiveVal
#' r <- reactiveVal()
#'
#' # Set the value by calling with an argument
#' r(10)
#'
#' # Read the value by calling without arguments
#' r()
#'
#' }
#'
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' actionButton("minus", "-1"),
#' actionButton("plus", "+1"),
#' br(),
#' textOutput("value")
#' )
#'
#' # The comments below show the equivalent logic using reactiveValues()
#' server <- function(input, output, session) {
#' value <- reactiveVal(0) # rv <- reactiveValues(value = 0)
#'
#' observeEvent(input$minus, {
#' newValue <- value() - 1 # newValue <- rv$value - 1
#' value(newValue) # rv$value <- newValue
#' })
#'
#' observeEvent(input$plus, {
#' newValue <- value() + 1 # newValue <- rv$value + 1
#' value(newValue) # rv$value <- newValue
#' })
#'
#' output$value <- renderText({
#' value() # rv$value
#' })
#' }
#'
#' shinyApp(ui, server)
#'
#' }
#'
#' @export
reactiveVal <- function(value = NULL, label = NULL) {
if (missing(label)) {
call <- sys.call()
label <- rvalSrcrefToLabel(attr(call, "srcref", exact = TRUE))
}
rv <- ReactiveVal$new(value, label)
structure(
function(x) {
if (missing(x)) {
rv$get()
} else {
force(x)
rv$set(x)
}
},
class = c("reactiveVal", "reactive"),
label = label,
.impl = rv
)
}
#' @rdname freezeReactiveValue
#' @export
freezeReactiveVal <- function(x) {
domain <- getDefaultReactiveDomain()
if (is.null(domain)) {
stop("freezeReactiveVal() must be called when a default reactive domain is active.")
}
if (!inherits(x, "reactiveVal")) {
stop("x must be a reactiveVal object")
}
attr(x, ".impl", exact = TRUE)$freeze(domain)
invisible()
}
#' @export
format.reactiveVal <- function(x, ...) {
attr(x, ".impl", exact = TRUE)$format(...)
}
# Attempts to extract the variable name that the reactiveVal object is being
# assigned to (e.g. for `a <- reactiveVal()`, the result should be "a"). This
# is a fragile, error-prone operation, so we default to a random label if
# necessary.
rvalSrcrefToLabel <- function(srcref,
defaultLabel = paste0("reactiveVal", createUniqueId(4))) {
if (is.null(srcref))
return(defaultLabel)
srcfile <- attr(srcref, "srcfile", exact = TRUE)
if (is.null(srcfile))
return(defaultLabel)
if (is.null(srcfile$lines))
return(defaultLabel)
lines <- srcfile$lines
# When pasting at the Console, srcfile$lines is not split
if (length(lines) == 1) {
lines <- strsplit(lines, "\n")[[1]]
}
if (length(lines) < srcref[1]) {
return(defaultLabel)
}
firstLine <- substring(lines[srcref[1]], srcref[2] - 1)
m <- regexec("\\s*([^[:space:]]+)\\s*(<-|=)\\s*reactiveVal\\b", firstLine)
if (m[[1]][1] == -1) {
return(defaultLabel)
}
sym <- regmatches(firstLine, m)[[1]][2]
res <- try(parse(text = sym), silent = TRUE)
if (inherits(res, "try-error"))
return(defaultLabel)
if (length(res) != 1)
return(defaultLabel)
return(as.character(res))
}
# ReactiveValues ------------------------------------------------------------
ReactiveValues <- R6Class(
@@ -397,14 +619,17 @@ str.reactivevalues <- function(object, indent.str = " ", ...) {
#' Freeze a reactive value
#'
#' This freezes a reactive value. If the value is accessed while frozen, a
#' These functions freeze a \code{\link{reactiveVal}}, or an element of a
#' \code{\link{reactiveValues}}. If the value is accessed while frozen, a
#' "silent" exception is raised and the operation is stopped. This is the same
#' thing that happens if \code{req(FALSE)} is called. The value is thawed
#' (un-frozen; accessing it will no longer raise an exception) when the current
#' reactive domain is flushed. In a Shiny application, this occurs after all of
#' the observers are executed.
#'
#' @param x A \code{\link{reactiveValues}} object (like \code{input}).
#' @param x For \code{freezeReactiveValue}, a \code{\link{reactiveValues}}
#' object (like \code{input}); for \code{freezeReactiveVal}, a
#' \code{\link{reactiveVal}} object.
#' @param name The name of a value in the \code{\link{reactiveValues}} object.
#'
#' @seealso \code{\link{req}}
@@ -446,7 +671,7 @@ str.reactivevalues <- function(object, indent.str = " ", ...) {
#' @export
freezeReactiveValue <- function(x, name) {
domain <- getDefaultReactiveDomain()
if (is.null(getDefaultReactiveDomain)) {
if (is.null(domain)) {
stop("freezeReactiveValue() must be called when a default reactive domain is active.")
}
@@ -461,6 +686,7 @@ Observable <- R6Class(
'Observable',
portable = FALSE,
public = list(
.origFunc = 'function',
.func = 'function',
.label = character(0),
.domain = NULL,
@@ -490,6 +716,7 @@ Observable <- R6Class(
funcLabel <- paste0("<reactive:", label, ">")
}
.origFunc <<- func
.func <<- wrapFunctionLabel(func, funcLabel,
..stacktraceon = ..stacktraceon)
.label <<- label
@@ -520,6 +747,10 @@ Observable <- R6Class(
else
invisible(.value)
},
format = function() {
label <- sprintf('reactive(%s)', paste(deparse(body(.origFunc)), collapse='\n'))
strsplit(label, "\n")[[1]]
},
.updateValue = function() {
ctx <- Context$new(.domain, .label, type = 'observable',
prevId = .mostRecentCtxId)
@@ -629,13 +860,13 @@ reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
# Attach a label and a reference to the original user source for debugging
srcref <- attr(substitute(x), "srcref", exact = TRUE)
if (is.null(label)) {
label <- srcrefToLabel(srcref[[1]],
label <- rexprSrcrefToLabel(srcref[[1]],
sprintf('reactive(%s)', paste(deparse(body(fun)), collapse='\n')))
}
if (length(srcref) >= 2) attr(label, "srcref") <- srcref[[2]]
attr(label, "srcfile") <- srcFileOfRef(srcref[[1]])
o <- Observable$new(fun, label, domain, ..stacktraceon = ..stacktraceon)
structure(o$getValue, observable = o, class = "reactive")
structure(o$getValue, observable = o, class = c("reactiveExpr", "reactive"))
}
# Given the srcref to a reactive expression, attempts to figure out what the
@@ -643,7 +874,7 @@ reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
# scans the line of code that started the reactive block and looks for something
# that looks like assignment. If we fail, fall back to a default value (likely
# the block of code in the body of the reactive).
srcrefToLabel <- function(srcref, defaultLabel) {
rexprSrcrefToLabel <- function(srcref, defaultLabel) {
if (is.null(srcref))
return(defaultLabel)
@@ -681,19 +912,25 @@ srcrefToLabel <- function(srcref, defaultLabel) {
return(as.character(res))
}
#' @export
format.reactiveExpr <- function(x, ...) {
attr(x, "observable", exact = TRUE)$format()
}
#' @export
print.reactive <- function(x, ...) {
label <- attr(x, "observable", exact = TRUE)$.label
cat(label, "\n")
cat(paste(format(x), collapse = "\n"), "\n")
}
#' @export
#' @rdname reactive
is.reactive <- function(x) inherits(x, "reactive")
is.reactive <- function(x) {
inherits(x, "reactive")
}
# Return the number of times that a reactive expression or observer has been run
execCount <- function(x) {
if (is.reactive(x))
if (inherits(x, "reactiveExpr"))
return(attr(x, "observable", exact = TRUE)$.execCount)
else if (inherits(x, 'Observer'))
return(x$.execCount)
@@ -1151,6 +1388,10 @@ setAutoflush <- local({
#' }
#' @export
reactiveTimer <- function(intervalMs=1000, session = getDefaultReactiveDomain()) {
# Need to make sure that session is resolved at creation, not when the
# callback below is fired (see #1621).
force(session)
dependents <- Map$new()
timerCallbacks$schedule(intervalMs, function() {
# Quit if the session is closed
@@ -1300,9 +1541,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()
#' })
@@ -1377,7 +1631,7 @@ reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
#' # Cross-session reactive file reader. In this example, all sessions share
#' # the same reader, so read.csv only gets executed once no matter how many
#' # user sessions are connected.
#' fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
#' fileData <- reactiveFileReader(1000, NULL, 'data.csv', read.csv)
#' function(input, output, session) {
#' output$data <- renderTable({
#' fileData()
@@ -1531,7 +1785,7 @@ maskReactiveContext <- function(expr) {
#' invalidations that come from its reactive dependencies; it only invalidates
#' in response to the given event.
#'
#' @section \code{ignoreNULL} and \code{ignoreInit}:
#' @section ignoreNULL and ignoreInit:
#'
#' Both \code{observeEvent} and \code{eventReactive} take an \code{ignoreNULL}
#' parameter that affects behavior when the \code{eventExpr} evaluates to

View File

@@ -287,17 +287,30 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
# .. ..$ y: NULL
# ..$ mapping: Named list()
#
# For ggplot2, it might be something like:
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
# str(getGgplotCoordmap(p, 1))
# For ggplot2, first you need to define the print.ggplot function from inside
# renderPlot, then use it to print the plot:
# print.ggplot <- function(x) {
# grid::grid.newpage()
#
# build <- ggplot2::ggplot_build(x)
#
# gtable <- ggplot2::ggplot_gtable(build)
# grid::grid.draw(gtable)
#
# structure(list(
# build = build,
# gtable = gtable
# ), class = "ggplot_build_gtable")
# }
#
# p <- print(ggplot(mtcars, aes(wt, mpg)) + geom_point())
# str(getGgplotCoordmap(p, 1, 72))
# List of 1
# $ :List of 10
# ..$ panel : int 1
# ..$ row : int 1
# ..$ col : int 1
# ..$ panel_vars: Named list()
# ..$ scale_x : int 1
# ..$ scale_y : int 1
# ..$ log :List of 2
# .. ..$ x: NULL
# .. ..$ y: NULL
@@ -320,8 +333,8 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
# can be up to two of them.
# mtc <- mtcars
# mtc$am <- factor(mtc$am)
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + facet_wrap(~ am)
# str(getGgplotCoordmap(p, 1))
# p <- print(ggplot(mtc, aes(wt, mpg)) + geom_point() + facet_wrap(~ am))
# str(getGgplotCoordmap(p, 1, 72))
# List of 2
# $ :List of 10
# ..$ panel : int 1
@@ -329,8 +342,6 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
# ..$ col : int 1
# ..$ panel_vars:List of 1
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 1
# ..$ scale_x : int 1
# ..$ scale_y : int 1
# ..$ log :List of 2
# .. ..$ x: NULL
# .. ..$ y: NULL
@@ -354,8 +365,6 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
# ..$ col : int 2
# ..$ panel_vars:List of 1
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 2
# ..$ scale_x : int 1
# ..$ scale_y : int 1
# ..$ log :List of 2
# .. ..$ x: NULL
# .. ..$ y: NULL
@@ -418,81 +427,191 @@ getPrevPlotCoordmap <- function(width, height) {
# Given a ggplot_build_gtable object, return a coordmap for it.
getGgplotCoordmap <- function(p, pixelratio, res) {
# Structure of ggplot objects changed after 2.1.0
new_ggplot <- (utils::packageVersion("ggplot2") > "2.1.0")
if (!inherits(p, "ggplot_build_gtable"))
return(NULL)
tryCatch({
# Get info from built ggplot object
info <- find_panel_info(p$build)
# Get ranges from gtable - it's possible for this to return more elements than
# info, because it calculates positions even for panels that aren't present.
# This can happen with facet_wrap.
ranges <- find_panel_ranges(p$gtable, pixelratio, res)
for (i in seq_along(info)) {
info[[i]]$range <- ranges[[i]]
}
return(info)
}, error = function(e) {
# If there was an error extracting info from the ggplot object, just return
# a list with the error message.
return(structure(list(), error = e$message))
})
}
find_panel_info <- function(b) {
# Structure of ggplot objects changed after 2.1.0. After 2.2.1, there was a
# an API for extracting the necessary information.
ggplot_ver <- utils::packageVersion("ggplot2")
if (ggplot_ver > "2.2.1") {
find_panel_info_api(b)
} else if (ggplot_ver > "2.1.0") {
find_panel_info_non_api(b, ggplot_format = "new")
} else {
find_panel_info_non_api(b, ggplot_format = "old")
}
}
# 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.
find_panel_info <- function(b) {
if (new_ggplot) {
layout <- b$layout$panel_layout
} else {
layout <- b$panel$layout
}
# Convert factor to numbers
layout$PANEL <- as.integer(as.character(layout$PANEL))
layout <- colon_colon("ggplot2", "summarise_layout")(b)
coord <- colon_colon("ggplot2", "summarise_coord")(b)
layers <- colon_colon("ggplot2", "summarise_layers")(b)
# Names of facets
facet_vars <- NULL
if (new_ggplot) {
facet <- b$layout$facet
if (inherits(facet, "FacetGrid")) {
facet_vars <- vapply(c(facet$params$cols, facet$params$rows), as.character, character(1))
} else if (inherits(facet, "FacetWrap")) {
facet_vars <- vapply(facet$params$facets, as.character, character(1))
}
} else {
facet <- b$plot$facet
if (inherits(facet, "grid")) {
facet_vars <- vapply(c(facet$cols, facet$rows), as.character, character(1))
} else if (inherits(facet, "wrap")) {
facet_vars <- vapply(facet$facets, as.character, character(1))
# 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
# log transform.
get_log_bases <- function(xscale, yscale, coord) {
# Given a transform object, find the log base; if the transform object is
# NULL, or if it's not a log transform, return NA.
get_log_base <- function(trans) {
if (!is.null(trans) && grepl("^log-", trans$name)) {
environment(trans$transform)$base
} else {
NA_real_
}
}
# Iterate over each row in the layout data frame
lapply(seq_len(nrow(layout)), function(i) {
# Slice out one row
l <- layout[i, ]
scale_x <- l$SCALE_X
scale_y <- l$SCALE_Y
mapping <- find_plot_mappings(b)
# For each of the faceting variables, get the value of that variable in
# the current panel. Default to empty _named_ list so that it's sent as a
# JSON object, not array.
panel_vars <- list(a = NULL)[0]
for (i in seq_along(facet_vars)) {
var_name <- facet_vars[[i]]
vname <- paste0("panelvar", i)
mapping[[vname]] <- var_name
panel_vars[[vname]] <- l[[var_name]]
}
list(
panel = l$PANEL,
row = l$ROW,
col = l$COL,
panel_vars = panel_vars,
scale_x = scale_x,
scale_y = scale_x,
log = check_log_scales(b, scale_x, scale_y),
domain = find_panel_domain(b, l$PANEL, scale_x, scale_y),
mapping = mapping
)
})
# First look for log base in scale, then coord; otherwise NULL.
list(
x = get_log_base(xscale$trans) %OR% coord$xlog %OR% NULL,
y = get_log_base(yscale$trans) %OR% coord$ylog %OR% NULL
)
}
# Given x/y min/max, and the x/y scale objects, create a list that
# represents the domain. Note that the x/y min/max should be taken from
# the layout summary table, not the scale objects.
get_domain <- function(xmin, xmax, ymin, ymax, xscale, yscale) {
is_reverse <- function(scale) {
identical(scale$trans$name, "reverse")
}
domain <- list(
left = xmin,
right = xmax,
bottom = ymin,
top = ymax
)
if (is_reverse(xscale)) {
domain$left <- -domain$left
domain$right <- -domain$right
}
if (is_reverse(yscale)) {
domain$top <- -domain$top
domain$bottom <- -domain$bottom
}
domain
}
# Rename the items in vars to have names like panelvar1, panelvar2.
rename_panel_vars <- function(vars) {
for (i in seq_along(vars)) {
names(vars)[i] <- paste0("panelvar", i)
}
vars
}
get_mappings <- function(layers, layout, coord) {
# For simplicity, we'll just use the mapping from the first layer of the
# ggplot object. The original uses quoted expressions; convert to
# character.
mapping <- layers$mapping[[1]]
# lapply'ing as.character results in unexpected behavior for expressions
# like `wt/2`; deparse handles it correctly.
mapping <- lapply(mapping, deparse)
# If either x or y is not present, give it a NULL entry.
mapping <- mergeVectors(list(x = NULL, y = NULL), mapping)
# The names (not values) of panel vars are the same across all panels,
# so just look at the first one. Also, the order of panel vars needs
# to be reversed.
vars <- rev(layout$vars[[1]])
for (i in seq_along(vars)) {
mapping[[paste0("panelvar", i)]] <- names(vars)[i]
}
if (isTRUE(coord$flip)) {
mapping[c("x", "y")] <- mapping[c("y", "x")]
}
mapping
}
# Mapping is constant across all panels, so get it here and reuse later.
mapping <- get_mappings(layers, layout, coord)
# If coord_flip is used, these need to be swapped
flip_xy <- function(layout) {
l <- layout
l$xscale <- layout$yscale
l$yscale <- layout$xscale
l$xmin <- layout$ymin
l$xmax <- layout$ymax
l$ymin <- layout$xmin
l$ymax <- layout$xmax
l
}
if (coord$flip) {
layout <- flip_xy(layout)
}
# Iterate over each row in the layout data frame
lapply(seq_len(nrow(layout)), function(i) {
# Slice out one row, use it as a list. The (former) list-cols are still
# in lists, so we need to unwrap them.
l <- as.list(layout[i, ])
l$vars <- l$vars[[1]]
l$xscale <- l$xscale[[1]]
l$yscale <- l$yscale[[1]]
list(
panel = as.numeric(l$panel),
row = l$row,
col = l$col,
# Rename panel vars. They must also be in reversed order.
panel_vars = rename_panel_vars(rev(l$vars)),
log = get_log_bases(l$xscale, l$yscale, coord),
domain = get_domain(l$xmin, l$xmax, l$ymin, l$ymax, l$xscale, l$yscale),
mapping = mapping
)
})
}
# This is for ggplot2<=2.2.1, before an API was introduced for extracting
# information about the plot object. The "old" format was used before 2.1.0.
# The "new" format was used after 2.1.0, up to 2.2.1. The reason these two
# formats are mixed together in a single function is historical, and it's not
# worthwhile to separate them at this point.
find_panel_info_non_api <- function(b, ggplot_format) {
# Given a single range object (representing the data domain) from a built
# ggplot object, return the domain.
find_panel_domain <- function(b, panel_num, scalex_num = 1, scaley_num = 1) {
if (new_ggplot) {
if (ggplot_format == "new") {
range <- b$layout$panel_ranges[[panel_num]]
} else {
range <- b$panel$ranges[[panel_num]]
@@ -505,7 +624,7 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
)
# Check for reversed scales
if (new_ggplot) {
if (ggplot_format == "new") {
xscale <- b$layout$panel_scales$x[[scalex_num]]
yscale <- b$layout$panel_scales$y[[scaley_num]]
} else {
@@ -546,7 +665,7 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
y_names <- character(0)
# Continuous scales have a trans; discrete ones don't
if (new_ggplot) {
if (ggplot_format == "new") {
if (!is.null(b$layout$panel_scales$x[[scalex_num]]$trans))
x_names <- b$layout$panel_scales$x[[scalex_num]]$trans$name
if (!is.null(b$layout$panel_scales$y[[scaley_num]]$trans))
@@ -620,129 +739,220 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
mappings
}
# Given a gtable object, return the x and y ranges (in pixel dimensions)
find_panel_ranges <- function(g, pixelratio) {
# Given a vector of unit objects, return logical vector indicating which ones
# are "null" units. These units use the remaining available width/height --
# that is, the space not occupied by elements that have an absolute size.
is_null_unit <- function(x) {
# A vector of units can be either a list of individual units (a unit.list
# object), each with their own set of attributes, or an atomic vector with
# one set of attributes. ggplot2 switched from the former (in version
# 1.0.1) to the latter. We need to make sure that we get the correct
# result in both cases.
if (inherits(x, "unit.list")) {
# For ggplot2 <= 1.0.1
vapply(x, FUN.VALUE = logical(1), function(u) {
isTRUE(attr(u, "unit", exact = TRUE) == "null")
})
} else {
# For later versions of ggplot2
attr(x, "unit", exact = TRUE) == "null"
}
if (ggplot_format == "new") {
layout <- b$layout$panel_layout
} else {
layout <- b$panel$layout
}
# Convert factor to numbers
layout$PANEL <- as.integer(as.character(layout$PANEL))
# Names of facets
facet_vars <- NULL
if (ggplot_format == "new") {
facet <- b$layout$facet
if (inherits(facet, "FacetGrid")) {
facet_vars <- vapply(c(facet$params$cols, facet$params$rows), as.character, character(1))
} else if (inherits(facet, "FacetWrap")) {
facet_vars <- vapply(facet$params$facets, as.character, character(1))
}
# Workaround for a bug in the quartz device. If you have a 400x400 image and
# run `convertWidth(unit(1, "npc"), "native")`, the result will depend on
# res setting of the device. If res=72, then it returns 400 (as expected),
# but if, e.g., res=96, it will return 300, which is incorrect.
devScaleFactor <- 1
if (grepl("quartz", names(grDevices::dev.cur()), fixed = TRUE)) {
devScaleFactor <- res / 72
} else {
facet <- b$plot$facet
if (inherits(facet, "grid")) {
facet_vars <- vapply(c(facet$cols, facet$rows), as.character, character(1))
} else if (inherits(facet, "wrap")) {
facet_vars <- vapply(facet$facets, as.character, character(1))
}
# Convert a unit (or vector of units) to a numeric vector of pixel sizes
h_px <- function(x) {
devScaleFactor * grid::convertHeight(x, "native", valueOnly = TRUE)
}
w_px <- function(x) {
devScaleFactor * grid::convertWidth(x, "native", valueOnly = TRUE)
}
# Given a vector of relative sizes (in grid units), and a function for
# converting grid units to numeric pixels, return a numeric vector of
# pixel sizes.
find_px_sizes <- function(rel_sizes, unit_to_px) {
# Total pixels (in height or width)
total_px <- unit_to_px(grid::unit(1, "npc"))
# Calculate size of all panel(s) together. Panels (and only panels) have
# null size.
null_idx <- is_null_unit(rel_sizes)
# All the absolute heights. At this point, null heights are 0. We need to
# calculate them separately and add them in later.
px_sizes <- unit_to_px(rel_sizes)
# Total size for panels is image size minus absolute (non-panel) elements
panel_px_total <- total_px - sum(px_sizes)
# Divide up the total panel size up into the panels (scaled by size)
panel_sizes_rel <- as.numeric(rel_sizes[null_idx])
panel_sizes_rel <- panel_sizes_rel / sum(panel_sizes_rel)
px_sizes[null_idx] <- panel_px_total * panel_sizes_rel
abs(px_sizes)
}
px_heights <- find_px_sizes(g$heights, h_px)
px_widths <- find_px_sizes(g$widths, w_px)
# Convert to absolute pixel positions
x_pos <- cumsum(px_widths)
y_pos <- cumsum(px_heights)
# Match up the pixel dimensions to panels
layout <- g$layout
# For panels:
# * For facet_wrap, they'll be named "panel-1", "panel-2", etc.
# * For no facet or facet_grid, they'll just be named "panel". For
# facet_grid, we need to re-order the layout table. Assume that panel
# numbers go from left to right, then next row.
# Assign a number to each panel, corresponding to PANEl in the built ggplot
# object.
layout <- layout[grepl("^panel", layout$name), ]
layout <- layout[order(layout$t, layout$l), ]
layout$panel <- seq_len(nrow(layout))
# When using a HiDPI client on a Linux server, the pixel
# dimensions are doubled, so we have to divide the dimensions by
# `pixelratio`. When a HiDPI client is used on a Mac server (with
# the quartz device), the pixel dimensions _aren't_ doubled, even though
# the image has double size. In the latter case we don't have to scale the
# numbers down.
pix_ratio <- 1
if (!grepl("^quartz", names(grDevices::dev.cur()))) {
pix_ratio <- pixelratio
}
# Return list of lists, where each inner list has left, right, top, bottom
# values for a panel
lapply(seq_len(nrow(layout)), function(i) {
p <- layout[i, , drop = FALSE]
list(
left = x_pos[p$l - 1] / pix_ratio,
right = x_pos[p$r] / pix_ratio,
bottom = y_pos[p$b] / pix_ratio,
top = y_pos[p$t - 1] / pix_ratio
)
})
}
# Iterate over each row in the layout data frame
lapply(seq_len(nrow(layout)), function(i) {
# Slice out one row
l <- layout[i, ]
tryCatch({
# Get info from built ggplot object
info <- find_panel_info(p$build)
scale_x <- l$SCALE_X
scale_y <- l$SCALE_Y
# Get ranges from gtable - it's possible for this to return more elements than
# info, because it calculates positions even for panels that aren't present.
# This can happen with facet_wrap.
ranges <- find_panel_ranges(p$gtable, pixelratio)
mapping <- find_plot_mappings(b)
for (i in seq_along(info)) {
info[[i]]$range <- ranges[[i]]
# For each of the faceting variables, get the value of that variable in
# the current panel. Default to empty _named_ list so that it's sent as a
# JSON object, not array.
panel_vars <- list(a = NULL)[0]
for (i in seq_along(facet_vars)) {
var_name <- facet_vars[[i]]
vname <- paste0("panelvar", i)
mapping[[vname]] <- var_name
panel_vars[[vname]] <- l[[var_name]]
}
return(info)
}, error = function(e) {
# If there was an error extracting info from the ggplot object, just return
# a list with the error message.
return(structure(list(), error = e$message))
list(
panel = l$PANEL,
row = l$ROW,
col = l$COL,
panel_vars = panel_vars,
scale_x = scale_x,
scale_y = scale_x,
log = check_log_scales(b, scale_x, scale_y),
domain = find_panel_domain(b, l$PANEL, scale_x, scale_y),
mapping = mapping
)
})
}
# Given a gtable object, return the x and y ranges (in pixel dimensions)
find_panel_ranges <- function(g, pixelratio, res) {
# Given a vector of unit objects, return logical vector indicating which ones
# are "null" units. These units use the remaining available width/height --
# that is, the space not occupied by elements that have an absolute size.
is_null_unit <- function(x) {
# A vector of units can be either a list of individual units (a unit.list
# object), each with their own set of attributes, or an atomic vector with
# one set of attributes. ggplot2 switched from the former (in version
# 1.0.1) to the latter. We need to make sure that we get the correct
# result in both cases.
if (inherits(x, "unit.list")) {
# For ggplot2 <= 1.0.1
vapply(x, FUN.VALUE = logical(1), function(u) {
isTRUE(attr(u, "unit", exact = TRUE) == "null")
})
} else {
# For later versions of ggplot2
attr(x, "unit", exact = TRUE) == "null"
}
}
# Workaround for a bug in the quartz device. If you have a 400x400 image and
# run `convertWidth(unit(1, "npc"), "native")`, the result will depend on
# res setting of the device. If res=72, then it returns 400 (as expected),
# but if, e.g., res=96, it will return 300, which is incorrect.
devScaleFactor <- 1
if (grepl("quartz", names(grDevices::dev.cur()), fixed = TRUE)) {
devScaleFactor <- res / 72
}
# Convert a unit (or vector of units) to a numeric vector of pixel sizes
h_px <- function(x) {
devScaleFactor * grid::convertHeight(x, "native", valueOnly = TRUE)
}
w_px <- function(x) {
devScaleFactor * grid::convertWidth(x, "native", valueOnly = TRUE)
}
# Given a vector of relative sizes (in grid units), and a function for
# converting grid units to numeric pixels, return a list with: known pixel
# dimensions, scalable dimensions, and the overall space for the scalable
# objects.
find_size_info <- function(rel_sizes, unit_to_px) {
# Total pixels (in height or width)
total_px <- unit_to_px(grid::unit(1, "npc"))
# Calculate size of all panel(s) together. Panels (and only panels) have
# null size.
null_idx <- is_null_unit(rel_sizes)
# All the absolute heights. At this point, null heights are 0. We need to
# calculate them separately and add them in later.
px_sizes <- unit_to_px(rel_sizes)
# Mark the null heights as NA.
px_sizes[null_idx] <- NA_real_
# The plotting panels all are 'null' units.
null_sizes <- rep(NA_real_, length(rel_sizes))
null_sizes[null_idx] <- as.numeric(rel_sizes[null_idx])
# Total size allocated for panels is the total image size minus absolute
# (non-panel) elements.
panel_px_total <- total_px - sum(px_sizes, na.rm = TRUE)
# Size of a 1null unit
null_px <- abs(panel_px_total / sum(null_sizes, na.rm = TRUE))
# This returned list contains:
# * px_sizes: A vector of known pixel dimensions. The values that were
# null units will be assigned NA. The null units are ones that scale
# when the plotting area is resized.
# * null_sizes: A vector of the null units. All others will be assigned
# NA. The null units often are 1, but they may be any value, especially
# when using coord_fixed.
# * null_px: The size (in pixels) of a 1null unit.
# * null_px_scaled: The size (in pixels) of a 1null unit when scaled to
# fit a smaller dimension (used for plots with coord_fixed).
list(
px_sizes = abs(px_sizes),
null_sizes = null_sizes,
null_px = null_px,
null_px_scaled = null_px
)
}
# Given a size_info, return absolute pixel positions
size_info_to_px <- function(info) {
px_sizes <- info$px_sizes
null_idx <- !is.na(info$null_sizes)
px_sizes[null_idx] <- info$null_sizes[null_idx] * info$null_px_scaled
# If this direction is scaled down because of coord_fixed, we need to add an
# offset so that the pixel locations are centered.
offset <- (info$null_px - info$null_px_scaled) *
sum(info$null_sizes, na.rm = TRUE) / 2
# Get absolute pixel positions
cumsum(px_sizes) + offset
}
heights_info <- find_size_info(g$heights, h_px)
widths_info <- find_size_info(g$widths, w_px)
if (g$respect) {
# This is a plot with coord_fixed. The grid 'respect' option means to use
# the same pixel value for 1null, for width and height. We want the
# smaller of the two values -- that's what makes the plot fit in the
# viewport.
null_px_min <- min(heights_info$null_px, widths_info$null_px)
heights_info$null_px_scaled <- null_px_min
widths_info$null_px_scaled <- null_px_min
}
# Convert to absolute pixel positions
y_pos <- size_info_to_px(heights_info)
x_pos <- size_info_to_px(widths_info)
# Match up the pixel dimensions to panels
layout <- g$layout
# For panels:
# * For facet_wrap, they'll be named "panel-1", "panel-2", etc.
# * For no facet or facet_grid, they'll just be named "panel". For
# facet_grid, we need to re-order the layout table. Assume that panel
# numbers go from left to right, then next row.
# Assign a number to each panel, corresponding to PANEl in the built ggplot
# object.
layout <- layout[grepl("^panel", layout$name), ]
layout <- layout[order(layout$t, layout$l), ]
layout$panel <- seq_len(nrow(layout))
# When using a HiDPI client on a Linux server, the pixel
# dimensions are doubled, so we have to divide the dimensions by
# `pixelratio`. When a HiDPI client is used on a Mac server (with
# the quartz device), the pixel dimensions _aren't_ doubled, even though
# the image has double size. In the latter case we don't have to scale the
# numbers down.
pix_ratio <- 1
if (!grepl("^quartz", names(grDevices::dev.cur()))) {
pix_ratio <- pixelratio
}
# Return list of lists, where each inner list has left, right, top, bottom
# values for a panel
lapply(seq_len(nrow(layout)), function(i) {
p <- layout[i, , drop = FALSE]
list(
left = x_pos[p$l - 1] / pix_ratio,
right = x_pos[p$r] / pix_ratio,
bottom = y_pos[p$b] / pix_ratio,
top = y_pos[p$t - 1] / pix_ratio
)
})
}

View File

@@ -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)
@@ -561,7 +561,7 @@ runApp <- function(appDir=getwd(),
}, add = TRUE)
if (.globals$running) {
stop("Can't call `runApp()` from within `runApp()`. If your ,",
stop("Can't call `runApp()` from within `runApp()`. If your ",
"application code contains `runApp()`, please remove it.")
}
.globals$running <- TRUE

View File

@@ -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
@@ -201,12 +201,13 @@ workerId <- local({
#' }
#' \item{\code{singletons} - for internal use}
#' \item{\code{url_protocol}, \code{url_hostname}, \code{url_port},
#' \code{url_pathname}, \code{url_search}, and \code{url_hash_initial}
#' can be used to get the components of the URL that was requested by the
#' browser to load the Shiny app page. These values are from the
#' browser's perspective, so neither HTTP proxies nor Shiny Server will
#' affect these values. The \code{url_search} value may be used with
#' \code{\link{parseQueryString}} to access query string parameters.
#' \code{url_pathname}, \code{url_search}, \code{url_hash_initial}
#' and \code{url_hash} can be used to get the components of the URL
#' that was requested by the browser to load the Shiny app page.
#' These values are from the browser's perspective, so neither HTTP
#' proxies nor Shiny Server will affect these values. The
#' \code{url_search} value may be used with \code{\link{parseQueryString}}
#' to access query string parameters.
#' }
#' }
#' \code{clientData} also contains information about each output.
@@ -374,12 +375,24 @@ NULL
#' @seealso \url{http://shiny.rstudio.com/articles/modules.html}
#' @export
NS <- function(namespace, id = NULL) {
if (length(namespace) == 0)
ns_prefix <- character(0)
else
ns_prefix <- paste(namespace, collapse = ns.sep)
f <- function(id) {
if (length(id) == 0)
return(ns_prefix)
if (length(ns_prefix) == 0)
return(id)
paste(ns_prefix, id, sep = ns.sep)
}
if (missing(id)) {
function(id) {
paste(c(namespace, id), collapse = ns.sep)
}
f
} else {
paste(c(namespace, id), collapse = ns.sep)
f(id)
}
}
@@ -415,6 +428,7 @@ ShinySession <- R6Class(
restoreCallbacks = 'Callbacks',
restoredCallbacks = 'Callbacks',
bookmarkExclude = character(0), # Names of inputs to exclude from bookmarking
getBookmarkExcludeFuns = list(),
testMode = FALSE, # Are we running in test mode?
testExportExprs = list(),
@@ -579,6 +593,16 @@ ShinySession <- R6Class(
}) # withReactiveDomain
},
# Modules (scopes) call this to register a function that returns a vector
# of names to exclude from bookmarking. The function should return
# something like c("scope1-x", "scope1-y"). This doesn't use a Callback
# object because the return values of the functions are needed, but
# Callback$invoke() discards return values.
registerBookmarkExclude = function(fun) {
len <- length(private$getBookmarkExcludeFuns) + 1
private$getBookmarkExcludeFuns[[len]] <- fun
},
# Save output values and errors. This is only used for testing mode.
storeOutputValues = function(values = NULL) {
private$outputValues <- mergeVectors(private$outputValues, values)
@@ -628,6 +652,12 @@ ShinySession <- R6Class(
values$output <- private$outputValues[items]
}
# Filter out those outputs that have the snapshotExclude attribute.
exclude_idx <- vapply(names(values$output), function(name) {
isTRUE(attr(private$.outputs[[name]], "snapshotExclude", TRUE))
}, logical(1))
values$output <- values$output[!exclude_idx]
values$output <- sortByName(values$output)
}
@@ -757,7 +787,8 @@ ShinySession <- R6Class(
private$sendMessage(
config = list(
workerId = workerId(),
sessionId = self$token
sessionId = self$token,
user = self$user
)
)
},
@@ -825,7 +856,7 @@ ShinySession <- R6Class(
if (anyUnnamed(dots))
stop("exportTestValues: all arguments must be named.")
names(dots) <- vapply(names(dots), ns, character(1))
names(dots) <- ns(names(dots))
do.call(
.subset2(self, "exportTestValues"),
@@ -939,6 +970,12 @@ ShinySession <- R6Class(
restoredCallbacks$invoke(scopeState)
})
# Returns the excluded names with the scope's ns prefix on them.
private$registerBookmarkExclude(function() {
excluded <- scope$getBookmarkExclude()
ns(excluded)
})
scope
},
ns = function(id) {
@@ -1022,6 +1059,10 @@ ShinySession <- R6Class(
}
if (is.function(func)) {
# Extract any output attributes attached to the render function. These
# will be attached to the observer after it's created.
outputAttrs <- attr(func, "outputAttrs", TRUE)
funcFormals <- formals(func)
# ..stacktraceon matches with the top-level ..stacktraceoff.., because
# the observer we set up below has ..stacktraceon=FALSE
@@ -1099,6 +1140,12 @@ ShinySession <- R6Class(
private$invalidatedOutputValues$set(name, value)
}, suspended=private$shouldSuspend(name), label=label)
# If any output attributes were added to the render function attach
# them to observer.
lapply(names(outputAttrs), function(name) {
attr(obs, name) <- outputAttrs[[name]]
})
obs$onInvalidate(function() {
self$showProgress(name)
})
@@ -1260,8 +1307,12 @@ ShinySession <- R6Class(
private$bookmarkExclude <- names
},
getBookmarkExclude = function() {
private$bookmarkExclude
scopedExcludes <- lapply(private$getBookmarkExcludeFuns, function(f) f())
scopedExcludes <- unlist(scopedExcludes)
c(private$bookmarkExclude, scopedExcludes)
},
onBookmark = function(fun) {
if (!is.function(fun) || length(fun) != 1) {
stop("`fun` must be a function that takes one argument")
@@ -1402,8 +1453,9 @@ ShinySession <- R6Class(
)
)
},
updateQueryString = function(queryString) {
private$sendMessage(updateQueryString = list(queryString = queryString))
updateQueryString = function(queryString, mode) {
private$sendMessage(updateQueryString = list(
queryString = queryString, mode = mode))
},
resetBrush = function(brushId) {
private$sendMessage(
@@ -1810,6 +1862,16 @@ outputOptions <- function(x, 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.

View File

@@ -14,7 +14,7 @@ NULL
#' # now we can just write "static" content without withMathJax()
#' div("more math here $$\\sqrt{2}$$")
withMathJax <- function(...) {
path <- 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
path <- 'https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
tagList(
tags$head(
singleton(tags$script(src = path, type = 'text/javascript'))
@@ -45,7 +45,6 @@ renderPage <- function(ui, connection, showcase=0, testMode=FALSE) {
shiny_deps <- list(
htmlDependency("json2", "2014.02.04", c(href="shared"), script = "json2-min.js"),
htmlDependency("jquery", "1.12.4", c(href="shared"), script = "jquery.min.js"),
htmlDependency("babel-polyfill", "6.7.2", c(href="shared"), script = "babel-polyfill.min.js"),
htmlDependency("shiny", utils::packageVersion("shiny"), c(href="shared"),
script = if (getOption("shiny.minified", TRUE)) "shiny.min.js" else "shiny.js",
stylesheet = "shiny.css")

View File

@@ -88,6 +88,26 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
useRenderFunction(x, inline = inline)
}
#' Mark a render function with attributes that will be used by the output
#'
#' @inheritParams markRenderFunction
#' @param snapshotExclude If TRUE, exclude the output from test snapshots.
#'
#' @keywords internal
markOutputAttrs <- function(renderFunc, snapshotExclude = NULL) {
# Add the outputAttrs attribute if necessary
if (is.null(attr(renderFunc, "outputAttrs", TRUE))) {
attr(renderFunc, "outputAttrs") <- list()
}
if (!is.null(snapshotExclude)) {
attr(renderFunc, "outputAttrs")$snapshotExclude <- snapshotExclude
}
renderFunc
}
#' Image file output
#'
#' Renders a reactive image that is suitable for assigning to an \code{output}
@@ -410,7 +430,9 @@ downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()
renderFunc <- function(shinysession, name, ...) {
shinysession$registerDownload(name, filename, contentType, content)
}
markRenderFunction(downloadButton, renderFunc, outputArgs = outputArgs)
snapshotExclude(
markRenderFunction(downloadButton, renderFunc, outputArgs = outputArgs)
)
}
#' Table output with the JavaScript library DataTables

View File

@@ -18,7 +18,8 @@ licenseLink <- function(licenseName) {
"Artistic-2.0" = "http://www.r-project.org/Licenses/Artistic-2.0",
"BSD_2_clause" = "http://www.r-project.org/Licenses/BSD_2_clause",
"BSD_3_clause" = "http://www.r-project.org/Licenses/BSD_3_clause",
"MIT" = "http://www.r-project.org/Licenses/MIT")
"MIT" = "http://www.r-project.org/Licenses/MIT",
"CC-BY-SA-4.0" = "https://www.r-project.org/Licenses/CC-BY-SA-4.0")
if (exists(licenseName, where = licenses)) {
tags$a(href=licenses[[licenseName]], licenseName)
} else {

View File

@@ -452,16 +452,18 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL,
updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
selected = NULL, inline = FALSE,
type = 'checkbox') {
if (!is.null(choices))
choices <- choicesWithNames(choices)
if (!is.null(selected))
selected <- validateSelected(selected, choices, session$ns(inputId))
selected = NULL, inline = FALSE, type = NULL,
choiceNames = NULL, choiceValues = NULL) {
if (is.null(type)) stop("Please specify the type ('checkbox' or 'radio')")
options <- if (!is.null(choices)) {
args <- normalizeChoicesArgs(choices, choiceNames, choiceValues, mustExist = FALSE)
if (!is.null(selected)) selected <- as.character(selected)
options <- if (!is.null(args$choiceValues)) {
format(tagList(
generateOptions(session$ns(inputId), choices, selected, inline, type = type)
generateOptions(session$ns(inputId), selected, inline, type,
args$choiceNames, args$choiceValues)
))
}
@@ -510,9 +512,10 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
#' }
#' @export
updateCheckboxGroupInput <- function(session, inputId, label = NULL,
choices = NULL, selected = NULL,
inline = FALSE) {
updateInputOptions(session, inputId, label, choices, selected, inline)
choices = NULL, selected = NULL, inline = FALSE,
choiceNames = NULL, choiceValues = NULL) {
updateInputOptions(session, inputId, label, choices, selected,
inline, "checkbox", choiceNames, choiceValues)
}
@@ -552,10 +555,15 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL,
#' }
#' @export
updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
selected = NULL, inline = FALSE) {
selected = NULL, inline = FALSE,
choiceNames = NULL, choiceValues = NULL) {
# you must select at least one radio button
if (is.null(selected) && !is.null(choices)) selected <- choices[[1]]
updateInputOptions(session, inputId, label, choices, selected, inline, type = 'radio')
if (is.null(selected)) {
if (!is.null(choices)) selected <- choices[[1]]
else if (!is.null(choiceValues)) selected <- choiceValues[[1]]
}
updateInputOptions(session, inputId, label, choices, selected,
inline, 'radio', choiceNames, choiceValues)
}
@@ -601,8 +609,7 @@ updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
updateSelectInput <- function(session, inputId, label = NULL, choices = NULL,
selected = NULL) {
choices <- if (!is.null(choices)) choicesWithNames(choices)
if (!is.null(selected))
selected <- validateSelected(selected, choices, inputId)
if (!is.null(selected)) selected <- as.character(selected)
options <- if (!is.null(choices)) selectOptions(choices, selected)
message <- dropNulls(list(label = label, options = options, value = selected))
session$sendInputMessage(inputId, message)

View File

@@ -14,9 +14,9 @@ For an introduction and examples, visit the [Shiny Dev Center](http://shiny.rstu
* Shiny applications are automatically "live" in the same way that spreadsheets are live. Outputs change instantly as users modify inputs, without requiring a reload of the browser.
* Shiny user interfaces can be built entirely using R, or can be written directly in HTML, CSS, and JavaScript for more flexibility.
* 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/2.3.2/).
* 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!).

View File

@@ -17,7 +17,7 @@ fluidPage(
),
# Show a summary of the dataset and an HTML table with the
# requested number of observations
# requested number of observations
mainPanel(
verbatimTextOutput("summary"),

View File

@@ -10,7 +10,7 @@ function(input, output) {
#
# 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)
# (it only executes a single time)
#
datasetInput <- reactive({
switch(input$dataset,

View File

@@ -22,7 +22,7 @@ fluidPage(
# Show the caption, a summary of the dataset and an HTML
# table with the requested number of observations
# table with the requested number of observations
mainPanel(
h3(textOutput("caption", container = span)),

View File

@@ -18,8 +18,8 @@ fluidPage(
checkboxInput("outliers", "Show outliers", FALSE)
),
# Show the caption and plot of the requested variable against
# mpg
# Show the caption and plot of the requested variable against
# mpg
mainPanel(
h3(textOutput("caption")),

View File

@@ -23,16 +23,16 @@ fluidPage(
min = 1, max = 1000, value = c(200,500)),
# Provide a custom currency format for value display,
# with basic animation
# 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
# plus looping
sliderInput("animation", "Looping Animation:", 1, 2000, 1,
step = 10, animate=
animationOptions(interval=300, loop=TRUE))
step = 10, animate =
animationOptions(interval=300, loop=TRUE))
),
# Show a table summarizing the values entered

View File

@@ -14,7 +14,7 @@ function(input, output) {
if (is.null(inFile))
return(NULL)
read.csv(inFile$datapath, header=input$header, sep=input$sep,
quote=input$quote)
read.csv(inFile$datapath, header=input$header, sep=input$sep,
quote=input$quote)
})
}

View File

@@ -6,8 +6,8 @@ fluidPage(
sidebarPanel(
fileInput('file1', 'Choose CSV File',
accept=c('text/csv',
'text/comma-separated-values,text/plain',
'.csv')),
'text/comma-separated-values,text/plain',
'.csv')),
tags$hr(),
checkboxInput('header', 'Header', TRUE),
radioButtons('sep', 'Separator',

View File

@@ -12,8 +12,8 @@ function(input, output) {
output$downloadData <- downloadHandler(
filename = function() {
paste(input$dataset, '.csv', sep='')
},
paste(input$dataset, '.csv', sep='')
},
content = function(file) {
write.csv(datasetInput(), file)
}

View File

@@ -59,7 +59,8 @@ sd_section("UI Inputs",
"updateTabsetPanel",
"updateTextInput",
"updateTextAreaInput",
"updateQueryString"
"updateQueryString",
"getQueryString"
)
)
sd_section("UI Outputs",
@@ -121,6 +122,7 @@ sd_section("Reactive programming",
"reactive",
"observe",
"observeEvent",
"reactiveVal",
"reactiveValues",
"reactiveValuesToList",
"is.reactivevalues",
@@ -191,6 +193,8 @@ sd_section("Utility functions",
"parseQueryString",
"plotPNG",
"exportTestValues",
"snapshotExclude",
"markOutputAttrs",
"repeatable",
"shinyDeprecated",
"serverInfo",

File diff suppressed because one or more lines are too long

View File

@@ -141,6 +141,7 @@
line-height: 0 !important;
padding: 0 !important;
margin: 0 !important;
overflow: hidden;
outline: none !important;
z-index: -9999 !important;
background: none !important;

View File

@@ -1,6 +1,6 @@
// Ion.RangeSlider
// version 2.1.2 Build: 350
// © Denis Ineshin, 2015
// Ion.RangeSlider
// version 2.1.6 Build: 369
// © Denis Ineshin, 2016
// https://github.com/IonDen
//
// Project page: http://ionden.com/a/plugins/ion.rangeSlider/en.html
@@ -10,7 +10,17 @@
// http://ionden.com/a/plugins/licence-en.html
// =====================================================================================================================
;(function ($, document, window, navigator, undefined) {
;(function(factory) {
if (typeof define === "function" && define.amd) {
define(["jquery"], function (jQuery) {
return factory(jQuery, document, window, navigator);
});
} else if (typeof exports === "object") {
factory(require("jquery"), document, window, navigator);
} else {
factory(jQuery, document, window, navigator);
}
} (function ($, document, window, navigator, undefined) {
"use strict";
// =================================================================================================================
@@ -146,7 +156,7 @@
* @constructor
*/
var IonRangeSlider = function (input, options, plugin_count) {
this.VERSION = "2.1.2";
this.VERSION = "2.1.6";
this.input = input;
this.plugin_count = plugin_count;
this.current_plugin = 0;
@@ -161,12 +171,15 @@
this.no_diapason = false;
this.is_key = false;
this.is_update = false;
this.is_first_update = true;
this.is_start = true;
this.is_finish = false;
this.is_active = false;
this.is_resize = false;
this.is_click = false;
options = options || {};
// cache for links to all DOM elements
this.$cache = {
win: $(window),
@@ -318,6 +331,11 @@
};
// check if base element is input
if ($inp[0].nodeName !== "INPUT") {
console && console.warn && console.warn("Base element should be <input>!", $inp[0]);
}
// config from data-attributes extends js config
config_from_data = {
@@ -375,16 +393,15 @@
for (prop in config_from_data) {
if (config_from_data.hasOwnProperty(prop)) {
if (!config_from_data[prop] && config_from_data[prop] !== 0) {
if (config_from_data[prop] === undefined || config_from_data[prop] === "") {
delete config_from_data[prop];
}
}
}
// input value extends default config
if (val) {
if (val !== undefined && val !== "") {
val = val.split(config_from_data.input_values_separator || options.input_values_separator || ";");
if (val[0] && val[0] == +val[0]) {
@@ -416,6 +433,7 @@
// validate config, to be sure that all data types are correct
this.update_check = {};
this.validate();
@@ -447,7 +465,7 @@
/**
* Starts or updates the plugin instance
*
* @param is_update {boolean}
* @param [is_update] {boolean}
*/
init: function (is_update) {
this.no_diapason = false;
@@ -734,7 +752,6 @@
// callbacks call
if ($.contains(this.$cache.cont[0], e.target) || this.dragging) {
this.is_finish = true;
this.callOnFinish();
}
@@ -761,7 +778,7 @@
}
if (!target) {
target = this.target;
target = this.target || "from";
}
this.current_plugin = this.plugin_count;
@@ -948,6 +965,12 @@
this.calcPointerPercent();
var handle_x = this.getHandleX();
if (this.target === "both") {
this.coords.p_gap = 0;
handle_x = this.getHandleX();
}
if (this.target === "click") {
this.coords.p_gap = this.coords.p_handle / 2;
handle_x = this.getHandleX();
@@ -1035,7 +1058,7 @@
break;
}
handle_x = this.toFixed(handle_x + (this.coords.p_handle * 0.1));
handle_x = this.toFixed(handle_x + (this.coords.p_handle * 0.001));
this.coords.p_from_real = this.convertToRealPercent(handle_x) - this.coords.p_gap_left;
this.coords.p_from_real = this.calcWithStep(this.coords.p_from_real);
@@ -1313,13 +1336,6 @@
this.$cache.s_single[0].style.left = this.coords.p_single_fake + "%";
this.$cache.single[0].style.left = this.labels.p_single_left + "%";
if (this.options.values.length) {
this.$cache.input.prop("value", this.result.from_value);
} else {
this.$cache.input.prop("value", this.result.from);
}
this.$cache.input.data("from", this.result.from);
} else {
this.$cache.s_from[0].style.left = this.coords.p_from_fake + "%";
this.$cache.s_to[0].style.left = this.coords.p_to_fake + "%";
@@ -1332,18 +1348,13 @@
}
this.$cache.single[0].style.left = this.labels.p_single_left + "%";
if (this.options.values.length) {
this.$cache.input.prop("value", this.result.from_value + this.options.input_values_separator + this.result.to_value);
} else {
this.$cache.input.prop("value", this.result.from + this.options.input_values_separator + this.result.to);
}
this.$cache.input.data("from", this.result.from);
this.$cache.input.data("to", this.result.to);
}
this.writeToInput();
if ((this.old_from !== this.result.from || this.old_to !== this.result.to) && !this.is_start) {
this.$cache.input.trigger("change");
this.$cache.input.trigger("input");
}
this.old_from = this.result.from;
@@ -1353,9 +1364,10 @@
if (!this.is_resize && !this.is_update && !this.is_start && !this.is_finish) {
this.callOnChange();
}
if (this.is_key || this.is_click) {
if (this.is_key || this.is_click || this.is_first_update) {
this.is_key = false;
this.is_click = false;
this.is_first_update = false;
this.callOnFinish();
}
@@ -1467,6 +1479,8 @@
this.$cache.from[0].style.visibility = "visible";
} else if (this.target === "to") {
this.$cache.to[0].style.visibility = "visible";
} else if (!this.target) {
this.$cache.from[0].style.visibility = "visible";
}
this.$cache.single[0].style.visibility = "hidden";
max = to_left;
@@ -1561,25 +1575,57 @@
/**
* Write values to input element
*/
writeToInput: function () {
if (this.options.type === "single") {
if (this.options.values.length) {
this.$cache.input.prop("value", this.result.from_value);
} else {
this.$cache.input.prop("value", this.result.from);
}
this.$cache.input.data("from", this.result.from);
} else {
if (this.options.values.length) {
this.$cache.input.prop("value", this.result.from_value + this.options.input_values_separator + this.result.to_value);
} else {
this.$cache.input.prop("value", this.result.from + this.options.input_values_separator + this.result.to);
}
this.$cache.input.data("from", this.result.from);
this.$cache.input.data("to", this.result.to);
}
},
// =============================================================================================================
// Callbacks
callOnStart: function () {
this.writeToInput();
if (this.options.onStart && typeof this.options.onStart === "function") {
this.options.onStart(this.result);
}
},
callOnChange: function () {
this.writeToInput();
if (this.options.onChange && typeof this.options.onChange === "function") {
this.options.onChange(this.result);
}
},
callOnFinish: function () {
this.writeToInput();
if (this.options.onFinish && typeof this.options.onFinish === "function") {
this.options.onFinish(this.result);
}
},
callOnUpdate: function () {
this.writeToInput();
if (this.options.onUpdate && typeof this.options.onUpdate === "function") {
this.options.onUpdate(this.result);
}
@@ -1587,6 +1633,7 @@
// =============================================================================================================
// Service methods
@@ -1796,7 +1843,7 @@
},
toFixed: function (num) {
num = num.toFixed(9);
num = num.toFixed(20);
return +num;
},
@@ -1884,32 +1931,37 @@
o.from = o.min;
}
if (typeof o.to !== "number" || isNaN(o.from)) {
if (typeof o.to !== "number" || isNaN(o.to)) {
o.to = o.max;
}
if (o.type === "single") {
if (o.from < o.min) {
o.from = o.min;
}
if (o.from > o.max) {
o.from = o.max;
}
if (o.from < o.min) o.from = o.min;
if (o.from > o.max) o.from = o.max;
} else {
if (o.from < o.min || o.from > o.max) {
o.from = o.min;
}
if (o.to > o.max || o.to < o.min) {
o.to = o.max;
}
if (o.from > o.to) {
o.from = o.to;
if (o.from < o.min) o.from = o.min;
if (o.from > o.max) o.from = o.max;
if (o.to < o.min) o.to = o.min;
if (o.to > o.max) o.to = o.max;
if (this.update_check.from) {
if (this.update_check.from !== o.from) {
if (o.from > o.to) o.from = o.to;
}
if (this.update_check.to !== o.to) {
if (o.to < o.from) o.to = o.from;
}
}
if (o.from > o.to) o.from = o.to;
if (o.to < o.from) o.to = o.from;
}
if (typeof o.step !== "number" || isNaN(o.step) || !o.step || o.step < 0) {
@@ -2167,7 +2219,10 @@
for (i = 0; i < num; i++) {
label = this.$cache.grid_labels[i][0];
label.style.marginLeft = -this.coords.big_x[i] + "%";
if (this.coords.big_x[i] !== Number.POSITIVE_INFINITY) {
label.style.marginLeft = -this.coords.big_x[i] + "%";
}
}
},
@@ -2229,6 +2284,8 @@
this.options.from = this.result.from;
this.options.to = this.result.to;
this.update_check.from = this.result.from;
this.update_check.to = this.result.to;
this.options = $.extend(this.options, options);
this.validate();
@@ -2306,4 +2363,4 @@
};
}());
} (jQuery, document, window, navigator));
}));

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,7 @@
.shiny-code {
background-color: white;
margin-bottom: 0;
}
.shiny-code code {

View File

@@ -217,7 +217,7 @@
var app = document.getElementById("showcase-app-container");
$(app).animate({
width: appWidth + "px",
zoom: zoom
zoom: (zoom*100) + "%"
}, animate ? animateMs : 0);
};

View File

@@ -95,6 +95,13 @@ pre.shiny-text-output.noplaceholder:empty {
font-weight: inherit;
}
/* Work around MS Edge transition bug (issue #1637) */
@supports (-ms-ime-align:auto) {
.shiny-bound-output {
transition: 0;
}
}
.recalculating {
opacity: 0.3;
transition: opacity 250ms ease 500ms;

View File

@@ -10,6 +10,13 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var exports = window.Shiny = window.Shiny || {};
var origPushState = window.history.pushState;
window.history.pushState = function () {
var result = origPushState.apply(this, arguments);
$(document).trigger("pushstate");
return result;
};
$(document).on('submit', 'form:not([action])', function (e) {
e.preventDefault();
});
@@ -18,7 +25,18 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// Source file: ../srcjs/utils.js
function escapeHTML(str) {
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;").replace(/\//g, "&#x2F;");
var escaped = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
"/": "&#x2F;"
};
return str.replace(/[&<>'"\/]/g, function (m) {
return escaped[m];
});
}
function randomId() {
@@ -60,6 +78,18 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
}return str;
}
// Round to a specified number of significant digits.
function roundSignif(x) {
var digits = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
if (digits < 1) throw "Significant digits must be at least 1.";
// This converts to a string and back to a number, which is inelegant, but
// is less prone to FP rounding error than an alternate method which used
// Math.round().
return parseFloat(x.toPrecision(digits));
}
// Take a string with format "YYYY-MM-DD" and return a Date object.
// IE8 and QTWebKit don't support YYYY-MM-DD, but they support YYYY/MM/DD
function parseDate(dateString) {
@@ -200,6 +230,16 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
return val.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g, '\\$1');
};
// Maps a function over an object, preserving keys. Like the mapValues
// function from lodash.
function mapValues(obj, f) {
var newObj = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) newObj[key] = f(obj[key]);
}
return newObj;
}
//---------------------------------------------------------------------
// Source file: ../srcjs/browser.js
@@ -428,6 +468,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var self = this;
this.pendingData[name] = value;
if (!this.timerId && !this.reentrant) {
this.timerId = setTimeout(function () {
self.reentrant = true;
@@ -449,55 +490,78 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var InputNoResendDecorator = function InputNoResendDecorator(target, initialValues) {
this.target = target;
this.lastSentValues = initialValues || {};
this.lastSentValues = this.reset(initialValues);
};
(function () {
this.setInput = function (name, value) {
// Note that opts is not passed to setInput at this stage of the input
// decorator stack. If in the future this setInput keeps track of opts, it
// would be best not to store the `el`, because that could prevent it from
// being GC'd.
var _splitInputNameType = splitInputNameType(name);
var inputName = _splitInputNameType.name;
var inputType = _splitInputNameType.inputType;
var jsonValue = JSON.stringify(value);
if (this.lastSentValues[name] === jsonValue) return;
this.lastSentValues[name] = jsonValue;
if (this.lastSentValues[inputName] && this.lastSentValues[inputName].jsonValue === jsonValue && this.lastSentValues[inputName].inputType === inputType) {
return;
}
this.lastSentValues[inputName] = { jsonValue: jsonValue, inputType: inputType };
this.target.setInput(name, value);
};
this.reset = function (values) {
values = values || {};
var strValues = {};
$.each(values, function (key, value) {
strValues[key] = JSON.stringify(value);
});
this.lastSentValues = strValues;
this.reset = function () {
var values = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
// Given an object with flat name-value format:
// { x: "abc", "y.shiny.number": 123 }
// Create an object in cache format and save it:
// { x: { jsonValue: '"abc"', inputType: "" },
// y: { jsonValue: "123", inputType: "shiny.number" } }
var cacheValues = {};
for (var inputName in values) {
if (values.hasOwnProperty(inputName)) {
var _splitInputNameType2 = splitInputNameType(inputName);
var name = _splitInputNameType2.name;
var inputType = _splitInputNameType2.inputType;
cacheValues[name] = {
jsonValue: JSON.stringify(values[inputName]),
inputType: inputType
};
}
}
this.lastSentValues = cacheValues;
};
}).call(InputNoResendDecorator.prototype);
var InputDeferDecorator = function InputDeferDecorator(target) {
this.target = target;
this.pendingInput = {};
};
(function () {
this.setInput = function (name, value) {
if (/^\./.test(name)) this.target.setInput(name, value);else this.pendingInput[name] = value;
};
this.submit = function () {
for (var name in this.pendingInput) {
if (this.pendingInput.hasOwnProperty(name)) this.target.setInput(name, this.pendingInput[name]);
}
};
}).call(InputDeferDecorator.prototype);
var InputEventDecorator = function InputEventDecorator(target) {
this.target = target;
};
(function () {
this.setInput = function (name, value, immediate) {
this.setInput = function (name, value, opts) {
var evt = jQuery.Event("shiny:inputchanged");
var name2 = name.split(':');
evt.name = name2[0];
evt.inputType = name2.length > 1 ? name2[1] : '';
var input = splitInputNameType(name);
evt.name = input.name;
evt.inputType = input.inputType;
evt.value = value;
evt.binding = opts.binding;
evt.el = opts.el;
$(document).trigger(evt);
if (!evt.isDefaultPrevented()) {
name = evt.name;
if (evt.inputType !== '') name += ':' + evt.inputType;
this.target.setInput(name, evt.value, immediate);
// opts aren't passed along to lower levels in the input decorator
// stack.
this.target.setInput(name, evt.value);
}
};
}).call(InputEventDecorator.prototype);
@@ -507,9 +571,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
this.inputRatePolicies = {};
};
(function () {
this.setInput = function (name, value, immediate) {
this.setInput = function (name, value, opts) {
this.$ensureInit(name);
if (immediate) this.inputRatePolicies[name].immediateCall(name, value, immediate);else this.inputRatePolicies[name].normalCall(name, value, immediate);
if (opts.immediate) this.inputRatePolicies[name].immediateCall(name, value, opts);else this.inputRatePolicies[name].normalCall(name, value, opts);
};
this.setRatePolicy = function (name, mode, millis) {
if (mode === 'direct') {
@@ -523,11 +588,59 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
this.$ensureInit = function (name) {
if (!(name in this.inputRatePolicies)) this.setRatePolicy(name, 'direct');
};
this.$doSetInput = function (name, value) {
this.target.setInput(name, value);
this.$doSetInput = function (name, value, opts) {
this.target.setInput(name, value, opts);
};
}).call(InputRateDecorator.prototype);
var InputDeferDecorator = function InputDeferDecorator(target) {
this.target = target;
this.pendingInput = {};
};
(function () {
this.setInput = function (name, value, opts) {
if (/^\./.test(name)) this.target.setInput(name, value, opts);else this.pendingInput[name] = { value: value, opts: opts };
};
this.submit = function () {
for (var name in this.pendingInput) {
if (this.pendingInput.hasOwnProperty(name)) {
var input = this.pendingInput[name];
this.target.setInput(name, input.value, input.opts);
}
}
};
}).call(InputDeferDecorator.prototype);
var InputValidateDecorator = function InputValidateDecorator(target) {
this.target = target;
};
(function () {
this.setInput = function (name, value, opts) {
if (!name) throw "Can't set input with empty name.";
opts = addDefaultInputOpts(opts);
this.target.setInput(name, value, opts);
};
}).call(InputValidateDecorator.prototype);
// Merge opts with defaults, and return a new object.
function addDefaultInputOpts(opts) {
return $.extend({
immediate: false,
binding: null,
el: null
}, opts);
}
function splitInputNameType(name) {
var name2 = name.split(':');
return {
name: name2[0],
inputType: name2.length > 1 ? name2[1] : ''
};
}
//---------------------------------------------------------------------
// Source file: ../srcjs/shinyapp.js
@@ -537,6 +650,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// Cached input values
this.$inputValues = {};
// Input values at initialization (and reconnect)
this.$initialInput = {};
// Output bindings
this.$bindings = {};
@@ -559,11 +675,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
this.connect = function (initialInput) {
if (this.$socket) throw "Connect was already called on this application object";
$.extend(initialInput, {
// IE8 and IE9 have some limitations with data URIs
".clientdata_allowDataUriScheme": typeof WebSocket !== 'undefined'
});
this.$socket = this.createSocket();
this.$initialInput = initialInput;
$.extend(this.$inputValues, initialInput);
@@ -1128,7 +1239,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
});
addMessageHandler('config', function (message) {
this.config = message;
this.config = { workerId: message.workerId, sessionId: message.sessionId };
if (message.user) exports.user = message.user;
$(document).trigger('shiny:sessioninitialized');
});
addMessageHandler('busy', function (message) {
@@ -1183,7 +1296,46 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
});
addMessageHandler('updateQueryString', function (message) {
window.history.replaceState(null, null, message.queryString);
// leave the bookmarking code intact
if (message.mode === "replace") {
window.history.replaceState(null, null, message.queryString);
return;
}
var what = null;
if (message.queryString.charAt(0) === "#") what = "hash";else if (message.queryString.charAt(0) === "?") what = "query";else throw "The 'query' string must start with either '?' " + "(to update the query string) or with '#' (to " + "update the hash).";
var path = window.location.pathname;
var oldQS = window.location.search;
var oldHash = window.location.hash;
/* Barbara -- December 2016
Note: we could check if the new QS and/or hash are different
from the old one(s) and, if not, we could choose not to push
a new state (whether or not we would replace it is moot/
inconsequential). However, I think that it is better to
interpret each call to `updateQueryString` as representing
new state (even if the message.queryString is the same), so
that check isn't even performed as of right now.
*/
var relURL = path;
if (what === "query") relURL += message.queryString;else relURL += oldQS + message.queryString; // leave old QS if it exists
window.history.pushState(null, null, relURL);
// for the case when message.queryString has both a query string
// and a hash (`what = "hash"` allows us to trigger the
// hashchange event)
if (message.queryString.indexOf("#") !== -1) what = "hash";
// for the case when there was a hash before, but there isn't
// any hash now (e.g. for when only the query string is updated)
if (window.location.hash !== oldHash) what = "hash";
// This event needs to be triggered manually because pushState() never
// causes a hashchange event to be fired,
if (what === "hash") $(document).trigger("hashchange");
});
addMessageHandler("resetBrush", function (message) {
@@ -1260,13 +1412,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
if (typeof message.detail !== 'undefined') {
$progress.find('.progress-detail').text(message.detail);
}
if (typeof message.value !== 'undefined') {
if (message.value !== null) {
$progress.find('.progress').show();
$progress.find('.progress-bar').width(message.value * 100 + '%');
} else {
$progress.find('.progress').hide();
}
if (typeof message.value !== 'undefined' && message.value !== null) {
$progress.find('.progress').show();
$progress.find('.progress-bar').width(message.value * 100 + '%');
}
} else if (message.style === "old") {
// For old-style (Shiny <=0.13.2) progress indicators.
@@ -1278,13 +1426,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
if (typeof message.detail !== 'undefined') {
$progress.find('.progress-detail').text(message.detail);
}
if (typeof message.value !== 'undefined') {
if (message.value !== null) {
$progress.find('.progress').show();
$progress.find('.bar').width(message.value * 100 + '%');
} else {
$progress.find('.progress').hide();
}
if (typeof message.value !== 'undefined' && message.value !== null) {
$progress.find('.progress').show();
$progress.find('.bar').width(message.value * 100 + '%');
}
$progress.fadeIn();
@@ -2840,6 +2984,11 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// For reversed scales, the min and max can be reversed, so use findBox
// to ensure correct order.
state.boundsData = coordmap.findBox(minData, maxData);
// Round to 14 significant digits to avoid spurious changes in FP values
// (#1634).
state.boundsData = mapValues(state.boundsData, function (val) {
return roundSignif(val, 14);
});
// We also need to attach the data bounds and panel as data attributes, so
// that if the image is re-sent, we can grab the data bounds to create a new
@@ -3323,6 +3472,14 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
});
outputBindings.register(downloadLinkOutputBinding, 'shiny.downloadLink');
// Trigger shiny:filedownload event whenever a downloadButton/Link is clicked
$(document).on('click.shinyDownloadLink', 'a.shiny-download-link', function (e) {
var evt = jQuery.Event('shiny:filedownload');
evt.name = this.id;
evt.href = this.href;
$(document).trigger(evt);
});
//---------------------------------------------------------------------
// Source file: ../srcjs/output_binding_datatable.js
@@ -4699,9 +4856,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
};
}).call(IE8FileUploader.prototype);
var FileUploader = function FileUploader(shinyapp, id, files) {
var FileUploader = function FileUploader(shinyapp, id, files, el) {
this.shinyapp = shinyapp;
this.id = id;
this.el = el;
FileProcessor.call(this, files);
};
$.extend(FileUploader.prototype, FileProcessor.prototype);
@@ -4771,6 +4929,26 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
};
this.onComplete = function () {
var self = this;
var fileInfo = $.map(this.files, function (file, i) {
return {
name: file.name,
size: file.size,
type: file.type
};
});
// Trigger shiny:inputchanged. Unlike a normal shiny:inputchanged event,
// it's not possible to modify the information before the values get
// sent to the server.
var evt = jQuery.Event("shiny:inputchanged");
evt.name = this.id;
evt.value = fileInfo;
evt.binding = fileInputBinding;
evt.el = this.el;
evt.inputType = 'shiny.fileupload';
$(document).trigger(evt);
this.makeRequest('uploadEnd', [this.jobId, this.id], function (response) {
self.$setActive(false);
self.onProgress(null, 1);
@@ -4779,18 +4957,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
self.onError(error);
});
this.$bar().text('Finishing upload');
// Trigger event when all files are finished uploading.
var evt = jQuery.Event("shiny:fileuploaded");
evt.name = this.id;
evt.files = $.map(this.files, function (file, i) {
return {
name: file.name,
size: file.size,
type: file.type
};
});
$(document).trigger(evt);
};
this.onError = function (message) {
this.$setError(message || '');
@@ -4813,7 +4979,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
this.$container().css('visibility', visible ? 'visible' : 'hidden');
};
this.$setError = function (error) {
this.$bar().toggleClass('bar-danger', error !== null);
this.$bar().toggleClass('progress-bar-danger', error !== null);
if (error !== null) {
this.onProgress(null, 1);
this.$bar().text(error);
@@ -4857,7 +5023,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
/*jshint nonew:false */
new IE8FileUploader(exports.shinyapp, id, evt.target);
} else {
$el.data('currentUploader', new FileUploader(exports.shinyapp, id, files));
$el.data('currentUploader', new FileUploader(exports.shinyapp, id, files, evt.target));
}
}
@@ -5003,19 +5169,27 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var inputsRate = new InputRateDecorator(inputsEvent);
var inputsDefer = new InputDeferDecorator(inputsEvent);
// By default, use rate decorator
var inputs = inputsRate;
$('input[type="submit"], button[type="submit"]').each(function () {
var inputs;
if ($('input[type="submit"], button[type="submit"]').length > 0) {
// If there is a submit button on the page, use defer decorator
inputs = inputsDefer;
$(this).click(function (event) {
event.preventDefault();
inputsDefer.submit();
});
});
exports.onInputChange = function (name, value) {
inputs.setInput(name, value);
$('input[type="submit"], button[type="submit"]').each(function () {
$(this).click(function (event) {
event.preventDefault();
inputsDefer.submit();
});
});
} else {
// By default, use rate decorator
inputs = inputsRate;
}
inputs = new InputValidateDecorator(inputs);
exports.onInputChange = function (name, value, opts) {
opts = addDefaultInputOpts(opts);
inputs.setInput(name, value, opts);
};
var boundInputs = {};
@@ -5026,7 +5200,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var value = binding.getValue(el);
var type = binding.getType(el);
if (type) id = id + ":" + type;
inputs.setInput(id, value, !allowDeferred);
var opts = { immediate: !allowDeferred, binding: binding, el: el };
inputs.setInput(id, value, opts);
}
}
@@ -5035,7 +5211,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var bindings = inputBindings.getBindings();
var currentValues = {};
var inputItems = {};
for (var i = 0; i < bindings.length; i++) {
var binding = bindings[i].binding;
@@ -5049,7 +5225,14 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var type = binding.getType(el);
var effectiveId = type ? id + ":" + type : id;
currentValues[effectiveId] = binding.getValue(el);
inputItems[effectiveId] = {
value: binding.getValue(el),
opts: {
immediate: true,
binding: binding,
el: el
}
};
/*jshint loopfunc:true*/
var thisCallback = function () {
@@ -5078,14 +5261,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
binding: binding,
bindingType: 'input'
});
if (shinyapp.isConnected()) {
valueChangeCallback(binding, el, false);
}
}
}
return currentValues;
return inputItems;
}
function unbindInputs() {
@@ -5125,12 +5304,11 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
unbindOutputs(scope, includeSelf);
}
exports.bindAll = function (scope) {
// _bindAll alone returns initial values, it doesn't send them to the
// server. export.bindAll needs to send the values to the server, so we
// wrap _bindAll in a closure that does that.
var currentValues = _bindAll(scope);
$.each(currentValues, function (name, value) {
inputs.setInput(name, value);
// _bindAll returns input values; it doesn't send them to the server.
// export.bindAll needs to send the values to the server.
var currentInputItems = _bindAll(scope);
$.each(currentInputItems, function (name, item) {
inputs.setInput(name, item.value, item.opts);
});
// Not sure if the iframe stuff is an intrinsic part of bindAll, but bindAll
@@ -5173,7 +5351,16 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// Initialize all input objects in the document, before binding
initializeInputs(document);
var initialValues = _bindAll(document);
// The input values returned by _bindAll() each have a structure like this:
// { value: 123, opts: { ... } }
// We want to only keep the value. This is because when the initialValues is
// passed to ShinyApp.connect(), the ShinyApp object stores the
// initialValues object for the duration of the session, and the opts may
// have a reference to the DOM element, which would prevent it from being
// GC'd.
var initialValues = mapValues(_bindAll(document), function (x) {
return x.value;
});
// The server needs to know the size of each image and plot output element,
// in case it is auto-sizing
@@ -5327,12 +5514,28 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
initialValues['.clientdata_url_hostname'] = window.location.hostname;
initialValues['.clientdata_url_port'] = window.location.port;
initialValues['.clientdata_url_pathname'] = window.location.pathname;
// Send initial URL search (query string) and update it if it changes
initialValues['.clientdata_url_search'] = window.location.search;
$(window).on('pushstate', function (e) {
inputs.setInput('.clientdata_url_search', window.location.search);
});
$(window).on('popstate', function (e) {
inputs.setInput('.clientdata_url_search', window.location.search);
});
// This is only the initial value of the hash. The hash can change, but
// a reactive version of this isn't sent because w atching for changes can
// a reactive version of this isn't sent because watching for changes can
// require polling on some browsers. The JQuery hashchange plugin can be
// used if this capability is important.
initialValues['.clientdata_url_hash_initial'] = window.location.hash;
initialValues['.clientdata_url_hash'] = window.location.hash;
$(window).on('hashchange', function (e) {
inputs.setInput('.clientdata_url_hash', location.hash);
});
// The server needs to know what singletons were rendered as part of
// the page loading
@@ -5347,6 +5550,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
}
});
// IE8 and IE9 have some limitations with data URIs
initialValues['.clientdata_allowDataUriScheme'] = typeof WebSocket !== 'undefined';
// We've collected all the initial values--start the server process!
inputsNoResend.reset(initialValues);
shinyapp.connect(initialValues);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -41,4 +41,3 @@ into a namespaced one, by combining them with \code{ns.sep} in between.
\url{http://shiny.rstudio.com/articles/modules.html}
}
\keyword{datasets}

View File

@@ -24,8 +24,7 @@ detail message (if any). The detail message will be shown with a
de-emphasized appearance relative to \code{message}.}
\item{value}{A numeric value at which to set
the progress bar, relative to \code{min} and \code{max}.
\code{NULL} hides the progress bar, if it is currently visible.}
the progress bar, relative to \code{min} and \code{max}.}
\item{style}{Progress display style. If \code{"notification"} (the default),
the progress indicator will show using Shiny's notification API. If
@@ -112,4 +111,3 @@ shinyApp(ui, server)
\code{\link{withProgress}}
}
\keyword{datasets}

View File

@@ -80,4 +80,3 @@ specify \code{0} for \code{top}, \code{left}, \code{right}, and \code{bottom}
rather than the more obvious \code{width = "100\%"} and \code{height =
"100\%"}.
}

View File

@@ -56,7 +56,7 @@ shinyApp(ui, server)
\seealso{
\code{\link{observeEvent}} and \code{\link{eventReactive}}
Other input.elements: \code{\link{checkboxGroupInput}},
Other input elements: \code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{passwordInput}},
@@ -64,4 +64,3 @@ Other input.elements: \code{\link{checkboxGroupInput}},
\code{\link{sliderInput}}, \code{\link{submitButton}},
\code{\link{textAreaInput}}, \code{\link{textInput}}
}

View File

@@ -32,4 +32,3 @@ addResourcePath('datasets', system.file('data', package='datasets'))
\seealso{
\code{\link{singleton}}
}

View File

@@ -27,4 +27,3 @@ output.
registerInputHandler
}
\keyword{internal}

View File

@@ -70,4 +70,3 @@ shinyApp(ui, server)
\seealso{
\code{\link{enableBookmarking}} for more examples.
}

View File

@@ -21,4 +21,3 @@ It isn't necessary to call this function if you use
\code{\link{pageWithSidebar}}, and \code{\link{navbarPage}}, because they
already include the Bootstrap web dependencies.
}

View File

@@ -1,8 +1,8 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/bootstrap.R
\name{bootstrapPage}
\alias{basicPage}
\alias{bootstrapPage}
\alias{basicPage}
\title{Create a Bootstrap page}
\usage{
bootstrapPage(..., title = NULL, responsive = NULL, theme = NULL)
@@ -41,4 +41,3 @@ The \code{basicPage} function is deprecated, you should use the
\seealso{
\code{\link{fluidPage}}, \code{\link{fixedPage}}
}

View File

@@ -49,4 +49,3 @@ This generates an object representing brushing options, to be passed as the
\code{brush} argument of \code{\link{imageOutput}} or
\code{\link{plotOutput}}.
}

View File

@@ -69,4 +69,3 @@ using just the x or y variable, whichever is appropriate.
\seealso{
\code{\link{plotOutput}} for example usage.
}

View File

@@ -29,4 +29,3 @@ modules are easier to reuse and easier to reason about. See the article at
\seealso{
\url{http://shiny.rstudio.com/articles/modules.html}
}

View File

@@ -4,8 +4,8 @@
\alias{checkboxGroupInput}
\title{Checkbox Group Input Control}
\usage{
checkboxGroupInput(inputId, label, choices, selected = NULL, inline = FALSE,
width = NULL)
checkboxGroupInput(inputId, label, choices = NULL, selected = NULL,
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL)
}
\arguments{
\item{inputId}{The \code{input} slot that will be used to access the value.}
@@ -13,7 +13,9 @@ checkboxGroupInput(inputId, label, choices, selected = NULL, inline = FALSE,
\item{label}{Display label for the control, or \code{NULL} for no label.}
\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.}
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.}
\item{selected}{The values that should be initially selected, if any.}
@@ -21,6 +23,16 @@ are named then that name rather than the value is displayed to the user.}
\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.}
}
\value{
A list of HTML elements that can be added to a UI definition.
@@ -42,19 +54,39 @@ ui <- fluidPage(
tableOutput("data")
)
server <- function(input, output) {
server <- function(input, output, session) {
output$data <- renderTable({
mtcars[, c("mpg", input$variable), drop = FALSE]
}, rownames = TRUE)
}
shinyApp(ui, server)
ui <- fluidPage(
checkboxGroupInput("icons", "Choose icons:",
choiceNames =
list(icon("calendar"), icon("bed"),
icon("cog"), icon("bug")),
choiceValues =
list("calendar", "bed", "cog", "bug")
),
textOutput("txt")
)
server <- function(input, output, session) {
output$txt <- renderText({
icons <- paste(input$icons, collapse = ", ")
paste("You chose", icons)
})
}
shinyApp(ui, server)
}
}
\seealso{
\code{\link{checkboxInput}}, \code{\link{updateCheckboxGroupInput}}
Other input.elements: \code{\link{actionButton}},
Other input elements: \code{\link{actionButton}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
\code{\link{numericInput}}, \code{\link{passwordInput}},
@@ -62,4 +94,3 @@ Other input.elements: \code{\link{actionButton}},
\code{\link{sliderInput}}, \code{\link{submitButton}},
\code{\link{textAreaInput}}, \code{\link{textInput}}
}

View File

@@ -39,7 +39,7 @@ shinyApp(ui, server)
\seealso{
\code{\link{checkboxGroupInput}}, \code{\link{updateCheckboxInput}}
Other input.elements: \code{\link{actionButton}},
Other input elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{dateInput}}, \code{\link{dateRangeInput}},
\code{\link{fileInput}}, \code{\link{numericInput}},
@@ -48,4 +48,3 @@ Other input.elements: \code{\link{actionButton}},
\code{\link{submitButton}}, \code{\link{textAreaInput}},
\code{\link{textInput}}
}

View File

@@ -19,4 +19,3 @@ This generates an object representing click options, to be passed as the
\code{click} argument of \code{\link{imageOutput}} or
\code{\link{plotOutput}}.
}

View File

@@ -64,4 +64,3 @@ shinyApp(ui, server = function(input, output) { })
\seealso{
\code{\link{fluidRow}}, \code{\link{fixedRow}}.
}

View File

@@ -56,4 +56,3 @@ sidebarPanel(
)
)
}

View File

@@ -21,4 +21,3 @@ served over Shiny's HTTP server. This function works by using
\code{\link{addResourcePath}} to map the HTML dependency's directory to a
URL.
}

View File

@@ -97,7 +97,7 @@ shinyApp(ui, server = function(input, output) { })
\seealso{
\code{\link{dateRangeInput}}, \code{\link{updateDateInput}}
Other input.elements: \code{\link{actionButton}},
Other input elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}},
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
@@ -106,4 +106,3 @@ Other input.elements: \code{\link{actionButton}},
\code{\link{sliderInput}}, \code{\link{submitButton}},
\code{\link{textAreaInput}}, \code{\link{textInput}}
}

View File

@@ -114,7 +114,7 @@ shinyApp(ui, server = function(input, output) { })
\seealso{
\code{\link{dateInput}}, \code{\link{updateDateRangeInput}}
Other input.elements: \code{\link{actionButton}},
Other input elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{fileInput}}, \code{\link{numericInput}},
@@ -123,4 +123,3 @@ Other input.elements: \code{\link{actionButton}},
\code{\link{submitButton}}, \code{\link{textAreaInput}},
\code{\link{textInput}}
}

View File

@@ -23,4 +23,3 @@ This generates an object representing dobule-click options, to be passed as
the \code{dblclick} argument of \code{\link{imageOutput}} or
\code{\link{plotOutput}}.
}

View File

@@ -78,6 +78,7 @@ window.
time each subsequent event is considered is already after the time window
has expired.
}
\examples{
## Only run examples in interactive R sessions
if (interactive()) {
@@ -119,4 +120,3 @@ shinyApp(ui, server)
}
}

View File

@@ -3,8 +3,9 @@
\name{domains}
\alias{domains}
\alias{getDefaultReactiveDomain}
\alias{onReactiveDomainEnded}
\alias{withReactiveDomain}
\alias{onReactiveDomainEnded}
\alias{domains}
\title{Reactive domains}
\usage{
getDefaultReactiveDomain()
@@ -51,4 +52,3 @@ as a convenience function for registering callbacks. If the reactive domain
is \code{NULL} and \code{failIfNull} is \code{FALSE}, then the callback will
never be invoked.
}

View File

@@ -3,6 +3,7 @@
\name{downloadButton}
\alias{downloadButton}
\alias{downloadLink}
\alias{downloadLink}
\title{Create a download button or link}
\usage{
downloadButton(outputId, label = "Download", class = NULL, ...)
@@ -43,6 +44,5 @@ downloadLink('downloadData', 'Download')
}
\seealso{
downloadHandler
\code{\link{downloadHandler}}
}

View File

@@ -61,4 +61,3 @@ server <- function(input, output) {
shinyApp(ui, server)
}
}

View File

@@ -228,4 +228,3 @@ shinyApp(ui, server)
Also see \code{\link{updateQueryString}}.
}

View File

@@ -68,4 +68,3 @@ shinyApp(
)
}
}

View File

@@ -58,4 +58,3 @@ tripleA <- renderTriple({
isolate(tripleA())
# "text, text, text"
}

View File

@@ -4,7 +4,8 @@
\alias{fileInput}
\title{File Upload Control}
\usage{
fileInput(inputId, label, multiple = FALSE, accept = NULL, width = NULL)
fileInput(inputId, label, multiple = FALSE, accept = NULL, width = NULL,
buttonLabel = "Browse...", placeholder = "No file selected")
}
\arguments{
\item{inputId}{The \code{input} slot that will be used to access the value.}
@@ -20,6 +21,11 @@ what kind of files the server is expecting.}
\item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
see \code{\link{validateCssUnit}}.}
\item{buttonLabel}{The label used on the button. Can be text or an HTML tag
object.}
\item{placeholder}{The text to show before a file has been uploaded.}
}
\description{
Create a file upload control that can be used to upload one or more files.
@@ -84,7 +90,7 @@ shinyApp(ui, server)
}
}
\seealso{
Other input.elements: \code{\link{actionButton}},
Other input elements: \code{\link{actionButton}},
\code{\link{checkboxGroupInput}},
\code{\link{checkboxInput}}, \code{\link{dateInput}},
\code{\link{dateRangeInput}}, \code{\link{numericInput}},
@@ -93,4 +99,3 @@ Other input.elements: \code{\link{actionButton}},
\code{\link{submitButton}}, \code{\link{textAreaInput}},
\code{\link{textInput}}
}

View File

@@ -81,4 +81,3 @@ fillPage(
)
)
}

View File

@@ -1,8 +1,8 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/bootstrap-layout.R
\name{fillRow}
\alias{fillCol}
\alias{fillRow}
\alias{fillCol}
\title{Flex Box-based row/column layouts}
\usage{
fillRow(..., flex = 1, width = "100\%", height = "100\%")
@@ -75,4 +75,3 @@ shinyApp(ui, server)
}
}

View File

@@ -68,4 +68,3 @@ shinyApp(ui, server = function(input, output) { })
\seealso{
\code{\link{column}}
}

View File

@@ -34,4 +34,3 @@ shinyApp(ui, server = function(input, output) { })
\seealso{
\code{\link{verticalLayout}}
}

View File

@@ -102,4 +102,3 @@ shinyApp(ui, server = function(input, output) { })
\seealso{
\code{\link{column}}, \code{\link{sidebarLayout}}
}

View File

@@ -1,18 +1,24 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/reactives.R
\name{freezeReactiveValue}
\name{freezeReactiveVal}
\alias{freezeReactiveVal}
\alias{freezeReactiveValue}
\title{Freeze a reactive value}
\usage{
freezeReactiveVal(x)
freezeReactiveValue(x, name)
}
\arguments{
\item{x}{A \code{\link{reactiveValues}} object (like \code{input}).}
\item{x}{For \code{freezeReactiveValue}, a \code{\link{reactiveValues}}
object (like \code{input}); for \code{freezeReactiveVal}, a
\code{\link{reactiveVal}} object.}
\item{name}{The name of a value in the \code{\link{reactiveValues}} object.}
}
\description{
This freezes a reactive value. If the value is accessed while frozen, a
These functions freeze a \code{\link{reactiveVal}}, or an element of a
\code{\link{reactiveValues}}. If the value is accessed while frozen, a
"silent" exception is raised and the operation is stopped. This is the same
thing that happens if \code{req(FALSE)} is called. The value is thawed
(un-frozen; accessing it will no longer raise an exception) when the current
@@ -58,4 +64,3 @@ shinyApp(ui, server)
\seealso{
\code{\link{req}}
}

93
man/getQueryString.Rd Normal file
View File

@@ -0,0 +1,93 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/history.R
\name{getQueryString}
\alias{getQueryString}
\alias{getUrlHash}
\title{Get the query string / hash component from the URL}
\usage{
getQueryString(session = getDefaultReactiveDomain())
getUrlHash(session = getDefaultReactiveDomain())
}
\arguments{
\item{session}{A Shiny session object.}
}
\value{
For \code{getQueryString}, a named list. For example, the query
string \code{?param1=value1&param2=value2} becomes \code{list(param1 =
value1, param2 = value2)}. For \code{getUrlHash}, a character vector with
the hash (including the leading \code{#} symbol).
}
\description{
Two user friendly wrappers for getting the query string and the hash
component from the app's URL.
}
\details{
These can be particularly useful if you want to display different content
depending on the values in the query string / hash (e.g. instead of basing
the conditional on an input or a calculated reactive, you can base it on the
query string). However, note that, if you're changing the query string / hash
programatically from within the server code, you must use
\code{updateQueryString(_yourNewQueryString_, mode = "push")}. The default
\code{mode} for \code{updateQueryString} is \code{"replace"}, which doesn't
raise any events, so any observers or reactives that depend on it will
\emph{not} get triggered. However, if you're changing the query string / hash
directly by typing directly in the browser and hitting enter, you don't have
to worry about this.
}
\examples{
## Only run this example in interactive R sessions
if (interactive()) {
## App 1: getQueryString
## Printing the value of the query string
## (Use the back and forward buttons to see how the browser
## keeps a record of each state)
shinyApp(
ui = fluidPage(
textInput("txt", "Enter new query string"),
helpText("Format: ?param1=val1&param2=val2"),
actionButton("go", "Update"),
hr(),
verbatimTextOutput("query")
),
server = function(input, output, session) {
observeEvent(input$go, {
updateQueryString(input$txt, mode = "push")
})
output$query <- renderText({
query <- getQueryString()
queryText <- paste(names(query), query,
sep = "=", collapse=", ")
paste("Your query string is:\\n", queryText)
})
}
)
## App 2: getUrlHash
## Printing the value of the URL hash
## (Use the back and forward buttons to see how the browser
## keeps a record of each state)
shinyApp(
ui = fluidPage(
textInput("txt", "Enter new hash"),
helpText("Format: #hash"),
actionButton("go", "Update"),
hr(),
verbatimTextOutput("hash")
),
server = function(input, output, session) {
observeEvent(input$go, {
updateQueryString(input$txt, mode = "push")
})
output$hash <- renderText({
hash <- getUrlHash()
paste("Your hash is:\\n", hash)
})
}
)
}
}
\seealso{
\code{\link{updateQueryString}}
}

View File

@@ -21,4 +21,3 @@ Create a header panel containing an application title.
\examples{
headerPanel("Hello Shiny!")
}

View File

@@ -21,4 +21,3 @@ helpText("Note: while the data view will show only",
"the specified number of observations, the",
"summary will be based on the full dataset.")
}

View File

@@ -33,4 +33,3 @@ This generates an object representing hovering options, to be passed as the
\code{hover} argument of \code{\link{imageOutput}} or
\code{\link{plotOutput}}.
}

View File

@@ -42,4 +42,3 @@ tags$ul(
htmlOutput("summary", container = tags$li, class = "custom-li-output")
)
}

View File

@@ -47,4 +47,3 @@ For lists of available icons, see
\href{http://fontawesome.io/icons/}{http://fontawesome.io/icons/} and
\href{http://getbootstrap.com/components/#glyphicons}{http://getbootstrap.com/components/#glyphicons}.
}

View File

@@ -13,4 +13,3 @@ inputPanel(...)
A \code{\link{flowLayout}} with a grey border and light grey background,
suitable for wrapping inputs.
}

View File

@@ -86,4 +86,3 @@ shinyApp(ui, server)
\seealso{
\code{\link{removeUI}}
}

View File

@@ -40,4 +40,3 @@ function named \code{func} in the current environment.
Wraps \code{\link{exprToFunction}}; see that method's documentation
for more documentation and examples.
}

View File

@@ -63,4 +63,3 @@ shinyApp(ui, server)
\seealso{
\code{\link{reactiveTimer}} is a slightly less safe alternative.
}

View File

@@ -15,4 +15,3 @@ Checks whether its argument is a reactivevalues object.
\seealso{
\code{\link{reactiveValues}}.
}

View File

@@ -77,4 +77,3 @@ isolate(fun())
# isolate also works if the reactive expression accesses values from the
# input object, like input$x
}

View File

@@ -1,10 +1,10 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/app.R
\name{knitr_methods}
\alias{knit_print.reactive}
\alias{knitr_methods}
\alias{knit_print.shiny.appobj}
\alias{knit_print.shiny.render.function}
\alias{knitr_methods}
\alias{knit_print.reactive}
\title{Knitr S3 methods}
\usage{
knit_print.shiny.appobj(x, ...)
@@ -24,4 +24,3 @@ knit_print.reactive(x, ..., inline = FALSE)
These S3 methods are necessary to help Shiny applications and UI chunks embed
themselves in knitr/rmarkdown documents.
}

View File

@@ -27,4 +27,3 @@ mainPanel(
plotOutput("mpgPlot")
)
}

View File

@@ -30,4 +30,3 @@ observe(print(b()))
a <- 20
}
}

18
man/markOutputAttrs.Rd Normal file
View File

@@ -0,0 +1,18 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/shinywrappers.R
\name{markOutputAttrs}
\alias{markOutputAttrs}
\title{Mark a render function with attributes that will be used by the output}
\usage{
markOutputAttrs(renderFunc, snapshotExclude = 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.}
}
\description{
Mark a render function with attributes that will be used by the output
}
\keyword{internal}

View File

@@ -30,4 +30,3 @@ Shiny regarding what UI function is most commonly used with this type of
render function. This can be used in R Markdown documents to create complete
output widgets out of just the render function.
}

View File

@@ -21,4 +21,3 @@ default, an error).
\seealso{
\code{\link{isolate}}
}

View File

@@ -18,4 +18,3 @@ When clicked, a \code{modalButton} will dismiss the modal dialog.
\seealso{
\code{\link{modalDialog}} for examples.
}

View File

@@ -129,4 +129,3 @@ shinyApp(
)
}
}

Some files were not shown because too many files have changed in this diff Show More