Compare commits

..

190 Commits

Author SHA1 Message Date
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
Winston Chang
e75c99672d Update NEWS 2017-01-09 12:38:48 -06:00
Winston Chang
7faba72ebe Fix URL 2017-01-09 12:38:48 -06:00
Winston Chang
cbe8fc1bdf Bump version to 1.0.0 2017-01-09 12:38:48 -06:00
Winston Chang
f66a7660e2 Merge pull request #1529 from rstudio/feature/res-path-numeric-prefix
Relax naming requirements for addResourcePath
2017-01-09 12:28:07 -06:00
Winston Chang
5f3159a203 Add link to PR in NEWS 2017-01-09 12:25:32 -06:00
JJ Allaire
76aeda4436 refine regex 2017-01-09 12:32:12 -05:00
JJ Allaire
fa791cd28c Relax naming requirements for addResourcePath()
First character no longer needs to be a letter. See https://github.com/rstudio/tutor/issues/4 for discussion.
2017-01-09 11:04:51 -05:00
Winston Chang
d836c68ee5 Grunt 2017-01-03 16:17:48 -06:00
Winston Chang
519d90f0a7 Update NEWS 2017-01-03 16:17:28 -06:00
Winston Chang
26400be6f7 Pressing Esc in a modal in a gadget only closes the modal. Closes #1453 (#1523) 2017-01-03 17:14:31 -05:00
Winston Chang
92ba7e9d54 Update yarn install instructions 2017-01-03 14:29:43 -06:00
Winston Chang
25eafe1e69 NEWS: more info on testing 2017-01-03 14:16:55 -06:00
Winston Chang
118a9ca861 Update NEWS 2017-01-03 12:54:06 -06:00
Winston Chang
174a1fe834 Update to font-awesome 4.7.0 2017-01-03 12:47:23 -06:00
Winston Chang
1e0f3f40a9 Replace structure(NULL) with structure(list())
In R-devel 71841, structure(NULL) was deprecated.
2016-12-28 16:43:29 -06:00
Barbara Borges Ribeiro
19623694f5 Added skipFirst arg to observeEvent (#1494)
* added skipFirs arg to observeEvent

* create getCurrentObserver() function

* better NEWS entry

* made code more consistent

* implemented `once` param to `observeEvent`; extensive documentation for `getCurrentObserver`

* implement dig param to `getCurrentObserver`

* fix bug that was causing unit tests to fail

* take two

* git commit

* removed function getCurrentObserver

* delete .globals$currentObserver variable

* update docs

* typo

* remove dupes in index.r (bah humbug)

* rerun devtools::document
2016-12-19 15:51:19 -08:00
Winston Chang
55a16043e1 Merge pull request #1510 from rstudio/joe/feature/debounce
Add reactive debounce and throttle functions
2016-12-16 11:10:02 -06:00
Winston Chang
29943b7edd Merge pull request #1482 from rstudio/barbara/runapp
Fixes #1358: more informative error message when calling runApp inside of an app's app.R
2016-12-16 10:15:35 -06:00
Joe Cheng
a1e2af9533 Add debounce/throttle tests, priority arg 2016-12-15 14:52:07 -08:00
Barbara Borges Ribeiro
c350e2a668 Fixes #1358: more informative error message when calling runApp inside of an app's app.R (or inside ui.R or server.R). 2016-12-15 21:50:39 +00:00
Joe Cheng
e0868ba2ab Fix #1013: flushReact should be called after app loads (#1503)
* Fix #1013: flushReact should be called after app loads

* Add link to pull request
2016-12-15 13:16:18 -06:00
Joe Cheng
bcefd1fbd8 Fix #117: Reactive expressions hold on to memory for longer than necessary (#1504)
* Fix #117: Reactive expressions hold on to memory for longer than necessary

* Fix broken link

* Add link to pull request
2016-12-15 13:15:00 -06:00
Joe Cheng
f5fbad0abf Add link to pull request 2016-12-15 11:14:48 -08:00
Joe Cheng
95b1a197be Remove unnecessary namespace 2016-12-15 11:11:29 -08:00
Joe Cheng
39169a36f5 Wording tweaks 2016-12-15 11:10:28 -08:00
Joe Cheng
3b1a409f07 Remove unnecessary link qualifier 2016-12-15 11:01:35 -08:00
Joe Cheng
accd70d4b4 Add session$userData feature (#1513)
* Add session$userData

* Tweak wording of NEWS

* Fix broken links
2016-12-15 12:50:20 -06:00
Winston Chang
3c7f4b760f Merge pull request #1514 from rstudio/joe/bugfix/fix-broken-links
Fix unqualified links to other packages
2016-12-15 12:42:19 -06:00
Joe Cheng
f7d7ccfd2c Fix unqualified links to other packages
R-devel warns on this now, causes Travis to fail
2016-12-15 10:35:46 -08:00
Joe Cheng
de98a03887 Add limitations section to debounce/throttle docs 2016-12-13 17:48:36 -08:00
Joe Cheng
0e11c240cb Add magrittr as Suggests because of ?debounce example 2016-12-13 17:29:29 -08:00
Joe Cheng
c0a298e484 Add reactive debounce and throttle functions 2016-12-13 17:22:12 -08:00
Winston Chang
907b9a9862 Merge pull request #1480 from rstudio/barbara/verbatim
Closes #1357: verbatimTextOutput should optionally be hidden if no content
2016-12-07 10:13:04 -06:00
Barbara Borges Ribeiro
8d70d91cf4 fix #1487: better error handling for insertUI when selector does not match anything in DOM (do console log) 2016-12-07 05:10:43 +00:00
Barbara Borges Ribeiro
6fb86859ce use past tense in all NEWS.md entries 2016-12-07 04:55:08 +00:00
Barbara Borges Ribeiro
fe733b319f Fixes #969: allow navbarPage's fluid param to control both containers 2016-12-07 00:22:33 +00:00
Barbara Borges Ribeiro
08b58f3055 allow navbarPage's fluid param to control both the content *and* the header containers 2016-12-07 00:21:49 +00:00
Barbara Borges Ribeiro
9f6659f526 added a new arg (placeholder = FALSE) to verbatimTextOutput() so that by default it doesn't show up when it is empty; improved the documentation example 2016-12-07 00:02:40 +00:00
Barbara Borges Ribeiro
d28397df93 Fix #1359: shinyApp options argument ignored when passed to runApp (#1483) 2016-12-06 20:52:19 +00:00
Barbara Borges Ribeiro
2e1c37146b Add ... arg to downloadButton (merge #1492)
Add ... arg to downloadButton
2016-12-05 15:37:02 +00:00
Barbara Borges Ribeiro
903adc8f97 Added ability to pass arguments to the a tag function called inside downloadButton() and downloadLink(). Closes #986. 2016-12-05 15:35:31 +00:00
Winston Chang
fc7f454382 Merge pull request #1449 from rstudio/barbara/bugfix/insert-ui
Fix #1438: `unbindAll()` should not be called when inserting content with `insertUI()`
2016-12-01 15:45:23 -06:00
Winston Chang
ef35fc63a1 Grunt 2016-12-01 15:41:51 -06:00
Barbara Borges Ribeiro
52a193b183 unbindAll() should not be called when inserting content with insertUI() 2016-12-01 15:40:17 -06:00
Winston Chang
dad401a6ec Merge pull request #1464 from rstudio/testmode-inject-js
Add support for injecting JS code when in test mode
2016-12-01 14:52:12 -06:00
Winston Chang
ec3f8118db Grunt 2016-12-01 10:47:02 -06:00
Winston Chang
cfc0194c00 Sort input, output, export by name 2016-12-01 10:46:46 -06:00
Winston Chang
dd28f52301 Add sortByName function 2016-12-01 10:46:46 -06:00
Winston Chang
9dcbd532e6 Add getTestSnapshotBaseUrl function 2016-12-01 10:46:46 -06:00
Winston Chang
16b4a2cad2 Rename testEndpointUrl to testSnapshotUrl 2016-12-01 10:46:46 -06:00
Winston Chang
bd9d8a035a Change arguments from plural to singular 2016-12-01 10:46:46 -06:00
Winston Chang
d55ffb0212 Change default snapshot format to JSON 2016-12-01 10:46:46 -06:00
Winston Chang
e76ddfd005 Emit message when running in test mode 2016-12-01 10:46:46 -06:00
Winston Chang
59145a3b40 Add testmode as an option to runApp 2016-12-01 10:46:46 -06:00
Winston Chang
c993f5343b Bump version to 0.14.2.9001 2016-12-01 10:46:46 -06:00
Winston Chang
b62acec5ee Use singular form of input, export, and output 2016-12-01 10:46:46 -06:00
Winston Chang
b34ab9cdd5 Add shiny:fileuploaded JS event 2016-12-01 10:46:46 -06:00
Winston Chang
e0a8ab852e Update NEWS 2016-12-01 10:46:46 -06:00
Winston Chang
bd5ebd0e41 Remove token check 2016-12-01 10:46:46 -06:00
Winston Chang
661e21d25b Safer method for injecting code in test mode 2016-12-01 10:46:46 -06:00
Winston Chang
dc69a2bc94 Make sure test values are named vectors 2016-12-01 10:46:46 -06:00
Winston Chang
e6fec6b27d Rename variable 2016-12-01 10:46:46 -06:00
Winston Chang
27b92f9838 Add args to getTestEndpointUrl 2016-12-01 10:46:46 -06:00
Winston Chang
3446def4dd Basic code injection support 2016-12-01 10:46:46 -06:00
Barbara Borges Ribeiro
2700206715 Improve documentation for submitButton and change 07_widgets example to use an action button (#1475)
* update 07_widgets example

* improved documentation for submitButton (including a warnign section and an full-app example)

* typo

* update documentation based on Winton's feedback
2016-11-28 13:19:40 -06:00
Winston Chang
fdfc6f70f3 Merge branch 'barbara/contributing' 2016-11-22 14:56:25 -06:00
Winston Chang
065c288edb Edits to contribution guidelines 2016-11-22 14:56:12 -06:00
Barbara Borges Ribeiro
3121d2c23e mention support for the optgroup tag in the documention for selectInput (specifically in the choices arg). Added example app too. 2016-11-22 11:28:35 -06:00
Barbara Borges Ribeiro
7cd3bb524c add download attribute to the a tag that generates downloadButoon and downloadLink 2016-11-18 21:00:28 -08:00
Barbara Borges Ribeiro
6b8cc97779 drafted new contribution guidelines 2016-11-19 02:22:16 +00:00
Winston Chang
b7112a1edd Add link to NEWS 2016-11-10 15:26:46 -06:00
Jonathan
28965b7356 Render HTML dependency <meta> tag contents correctly (#1463)
* render HTML dependency <meta> tag contents correctly

* use direct address rather than loop; update NEWS
2016-11-10 15:10:18 -06:00
Dean Attali
bd3aa28416 fix typo in dateInput documentation (#1454) 2016-11-02 09:53:40 -05:00
Winston Chang
9fed4ce24c Bump version to 0.14.2.9000 2016-11-01 15:40:04 -05:00
Winston Chang
90383e30dd Bump version to 0.14.2 2016-10-31 10:19:40 -05:00
Winston Chang
13f184e957 Remove NEWS entry for change that was reverted 2016-10-31 10:17:51 -05:00
Winston Chang
a7a2c6d7ff Add list2env wrapper, for R <3.2.0 (#1446)
* Add list2env wrapper, for R <3.2.0

* Update NEWS
2016-10-28 13:56:52 -05:00
Winston Chang
d1bf39d0ac Add exportTestValues function (#1436)
* Add onTestSnapshot function

* Add shiny.testing option

* Add entry to staticdocs index

* Bump version to 0.14.1.9002 and update NEWS

* Document params for onTestSnapshot

* Add session$enableTestEndpoint() method

* Un-export applyInputHandlers

* Grunt

* Provide inputs, outputs, and snapshot at test endpoint

* Remove non-working example

* Fix var name in documentation

* Rename shiny.testing to shiny.testmode

* Rename onTestSnapshot to exportTestValues and add example

* Add session$getTestEndpointUrl

* Grunt

* Add module support to exportTestValues

* Test endpoint allows specifying specific values

* session$getTestEndpointUrl: add arguments for choosing which values to return
2016-10-27 21:08:34 -05:00
Joe Cheng
7dff6b8415 Merge pull request #1387 from sipemu/master
options render for updateSelectizeInput did not worked in modules
2016-10-27 11:11:16 -07:00
Dean Attali
656e019829 allow overriding a JS custom message handler; fixes #1419 (#1445)
* allow overriding existing custom JS message handlers

* when a JS handler gets re-defined, only use the most recent one

* JS handler overwrite: changes re winston's comments

* overwrite JS handler: add NEWS item

* fix wrong URL in NEWS
2016-10-27 13:07:34 -05:00
Winston Chang
2133b0f498 Use === in Javascript 2016-10-26 21:01:36 -05:00
Winston Chang
bc4dcee2b1 allow shiny.trace option to specify which type of messages to relay; fixes #1422 (#1428)
Squashed commit of the following:

commit bdc4080032ff6b5b2de0f799aa307272f3905003
Author: Dean Attali <daattali@gmail.com>
Date:   Mon Oct 17 18:18:03 2016 -0700

    add PR link to news item

commit 22c695cde2b270ba8ec37d4862ad1f30de76ce68
Author: Dean Attali <daattali@gmail.com>
Date:   Mon Oct 17 15:01:24 2016 -0700

    update NEWS for #1422 fix

commit e669548c13f84f0929e4131c641a8333e08baa26
Author: Dean Attali <daattali@gmail.com>
Date:   Sat Oct 15 12:45:49 2016 -0700

    allow shiny.trace option to specify which type of messages to relay; fixes #1422
2016-10-26 12:24:00 -05:00
Winston Chang
0e8cf95739 Pass shinysession to applyInputHandlers
This fixes a problem where input handlers that require a session object
would throw errors.
2016-10-25 10:27:03 -05:00
Simon Müller
6054f03c0d Update update-input.R 2016-09-21 22:53:41 +02:00
239 changed files with 7321 additions and 1921 deletions

View File

@@ -1,10 +1,38 @@
We welcome contributions to the **shiny** package. To submit a contribution:
1. [Fork](https://github.com/rstudio/shiny/fork) the repository and make your changes.
2. Ensure that you have signed the [individual](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioIndividualContributorAgreement.pdf) or [corporate](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioCorporateContributorAgreement.pdf) contributor agreement as appropriate. You can send the signed copy to jj@rstudio.com.
2. If the change is non-trivial, ensure that you have signed the [individual](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioIndividualContributorAgreement.pdf) or [corporate](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioCorporateContributorAgreement.pdf) contributor agreement as appropriate. You can send the signed copy to jj@rstudio.com. For trivial changes (like typo fixes), a contributor agreement is not needed.
3. Submit a [pull request](https://help.github.com/articles/using-pull-requests).
We'll try to be as responsive as possible in reviewing and accepting pull requests. We appreciate your contributions!
We generally do not merge pull requests that update included web libraries (such as Bootstrap or jQuery) because it is difficult for us to verify that the update is done correctly; we prefer to update these libraries ourselves.
## How to make changes
Before you submit a pull request, please do the following:
* Add an entry to NEWS.md concisely describing what you changed.
* If appropriate, add unit tests in the tests/ directory.
* If you made any changes to the JavaScript files in the srcjs/ directory, make sure you build the output JavaScript files. See tools/README.md file for information on using the build system.
* Run Build->Check Package in the RStudio IDE, or `devtools::check()`, to make sure your change did not add any messages, warnings, or errors.
Doing these things will make it easier for the Shiny development team to evaluate your pull request. Even so, we may still decide to modify your code or even not merge it at all. Factors that may prevent us from merging the pull request include:
* breaking backward compatibility
* adding a feature that we do not consider relevant for Shiny
* is hard to understand
* is hard to maintain in the future
* is computationally expensive
* is not intuitive for people to use
We will try to be responsive and provide feedback in case we decide not to merge your pull request.
## Filing issues
If you find a bug in Shiny, you can also [file an issue](https://github.com/rstudio/shiny/issues/new). Please provide as much relevant information as you can, and include a minimal reproducible example if possible.

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 0.14.1.9001
Version: 1.0.1
Authors@R: c(
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
@@ -79,7 +79,8 @@ Suggests:
knitr (>= 1.6),
markdown,
rmarkdown,
ggplot2
ggplot2,
magrittr
URL: http://shiny.rstudio.com
BugReports: https://github.com/rstudio/shiny/issues
Collate:
@@ -97,6 +98,9 @@ Collate:
'diagnose.R'
'fileupload.R'
'graph.R'
'reactives.R'
'reactive-domains.R'
'history.R'
'hooks.R'
'html-deps.R'
'htmltools.R'
@@ -128,8 +132,6 @@ Collate:
'priorityqueue.R'
'progress.R'
'react.R'
'reactive-domains.R'
'reactives.R'
'render-plot.R'
'render-table.R'
'run-url.R'
@@ -142,6 +144,7 @@ Collate:
'shinywrappers.R'
'showcase.R'
'tar.R'
'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)
@@ -38,7 +40,6 @@ export(actionButton)
export(actionLink)
export(addResourcePath)
export(animationOptions)
export(applyInputHandlers)
export(as.shiny.appobj)
export(basicPage)
export(bookmarkButton)
@@ -62,6 +63,7 @@ export(dataTableOutput)
export(dateInput)
export(dateRangeInput)
export(dblclickOpts)
export(debounce)
export(dialogViewer)
export(div)
export(downloadButton)
@@ -70,6 +72,7 @@ export(downloadLink)
export(em)
export(enableBookmarking)
export(eventReactive)
export(exportTestValues)
export(exprToFunction)
export(extractStackTrace)
export(fileInput)
@@ -83,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)
@@ -167,6 +173,7 @@ export(reactiveTable)
export(reactiveText)
export(reactiveTimer)
export(reactiveUI)
export(reactiveVal)
export(reactiveValues)
export(reactiveValuesToList)
export(registerInputHandler)
@@ -210,6 +217,7 @@ export(sidebarLayout)
export(sidebarPanel)
export(singleton)
export(sliderInput)
export(snapshotExclude)
export(span)
export(splitLayout)
export(stopApp)
@@ -229,6 +237,7 @@ export(tags)
export(textAreaInput)
export(textInput)
export(textOutput)
export(throttle)
export(titlePanel)
export(uiOutput)
export(updateActionButton)

153
NEWS.md
View File

@@ -1,18 +1,159 @@
shiny 0.14.1.9001
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
===========
Shiny has reached a milestone: version 1.0.0! In the last year, we've added two major features that we considered essential for a 1.0.0 release: bookmarking, and support for testing Shiny applications. As usual, this version of Shiny also includes many minor features and bug fixes.
Here are some highlights from this release. For more details, see the full changelog below.
## Support for testing Shiny applications
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
Now there's an official way to slow down reactive values and expressions that invalidate too quickly. Pass a reactive expression to the new `debounce` or `throttle` function, and get back a modified reactive expression that doesn't invalidate as often. ([#1510](https://github.com/rstudio/shiny/pull/1510))
## Full changelog
### Breaking changes
* Added a new `placeholder` argument to `verbatimTextOutput()`. The default is `FALSE`, which means that, if there is no content for this output, no representation of this slot will be made in the UI. Previsouly, even if there was no content, you'd see an empty rectangle in the UI that served as a placeholder. You can set `placeholder = TRUE` to revert back to that look. ([#1480](https://github.com/rstudio/shiny/pull/1480))
### New features
* Added support for testing Shiny applications with the shinytest package. ([#18](https://github.com/rstudio/shiny/issues/18), [#1464](https://github.com/rstudio/shiny/pull/1464))
* Added `debounce` and `throttle` functions, to control the rate at which reactive values and expressions invalidate. ([#1510](https://github.com/rstudio/shiny/pull/1510))
### Minor new features and improvements
* Addressed [#1486](https://github.com/rstudio/shiny/issues/1486) by adding a new argument to `observeEvent` and `eventReactive`, called `ignoreInit` (defaults to `FALSE` for backwards compatibility). When set to `TRUE`, the action (i.e. the second argument: `handlerExpr` and `valueExpr`, respectively) will not be triggered when the observer/reactive is first created/initialized. In other words, `ignoreInit = TRUE` ensures that the `observeEvent` (or `eventReactive`) is *never* run right away. For more info, see the documentation (`?observeEvent`). ([#1494](https://github.com/rstudio/shiny/pull/1494))
* Added a new argument to `observeEvent` called `once`. When set to `TRUE`, it results in the observer being destroyed (stop observing) after the first time that `handlerExpr` is run (i.e. `once = TRUE` guarantees that the observer only runs, at most, once). For more info, see the documentation (`?observeEvent`). ([#1494](https://github.com/rstudio/shiny/pull/1494))
* Addressed [#1358](https://github.com/rstudio/shiny/issues/1358): more informative error message when calling `runApp()` inside of an app's app.R (or inside ui.R or server.R). ([#1482](https://github.com/rstudio/shiny/pull/1482))
* Added a more descriptive JS warning for `insertUI()` when the selector argument does not match anything in DOM. ([#1488](https://github.com/rstudio/shiny/pull/1488))
* Added support for injecting JavaScript code when the `shiny.testmode` option is set to `TRUE`. This makes it possible to record test events interactively. ([#1464](https://github.com/rstudio/shiny/pull/1464))
* Added ability through arguments to the `a` tag function called inside `downloadButton()` and `downloadLink()`. Closes [#986](https://github.com/rstudio/shiny/issues/986). ([#1492](https://github.com/rstudio/shiny/pulls/1492))
* Implemented [#1512](https://github.com/rstudio/shiny/issues/1512): added a `userData` environment to `session`, for storing arbitrary session-related variables. Generally, session-scoped variables are created just by declaring normal variables that are local to the Shiny server function, but `session$userData` may be more convenient for some advanced scenarios. ([#1513](https://github.com/rstudio/shiny/pull/1513))
* Relaxed naming requirements for `addResourcePath()` (the first character no longer needs to be a letter). ([#1529](https://github.com/rstudio/shiny/pull/1529))
### Bug fixes
* Fixed [#969](https://github.com/rstudio/shiny/issues/969): allow navbarPage's `fluid` param to control both containers. ([#1481](https://github.com/rstudio/shiny/pull/1481))
* Fixed [#1438](https://github.com/rstudio/shiny/issues/1438): `unbindAll()` should not be called when inserting content with `insertUI()` ([#1449](https://github.com/rstudio/shiny/pull/1449))
* Fixed bug causing `<meta>` tags associated with HTML dependencies of Shiny R Markdown files to be rendered incorrectly. ([#1463](https://github.com/rstudio/shiny/pull/1463))
* Fixed [#1359](https://github.com/rstudio/shiny/issues/1359): `shinyApp()` options argument ignored when passed to `runApp()`. ([#1483](https://github.com/rstudio/shiny/pull/1483))
* Fixed [#117](https://github.com/rstudio/shiny/issues/117): Reactive expressions now release references to cached values as soon as they are invalidated, potentially making those cached values eligible for garbage collection sooner. Previously, this would not occur until the next cached value was calculated and stored. ([#1504](https://github.com/rstudio/shiny/pull/1504/files))
* Fixed [#1013](https://github.com/rstudio/shiny/issues/1013): `flushReact` should be called after app loads. Observers set up outside of server functions were not running until after the first user connects. ([#1503](https://github.com/rstudio/shiny/pull/1503))
* Fixed [#1453](https://github.com/rstudio/shiny/issues/1453): When using a modal dialog with `easyClose=TRUE` in a Shiny gadget, pressing Esc would close both the modal and the gadget. Now pressing Esc only closes the modal. ([#1523](https://github.com/rstudio/shiny/pull/1523))
### Library updates
* Updated to Font Awesome 4.7.0.
shiny 0.14.2
============
This is a maintenance release of Shiny, with some bug fixes and minor new features.
## Full changelog
### Minor new features and improvements
* HTML markup can now be used in the choices' names for radio buttons. ([#1439](https://github.com/rstudio/shiny/pull/1439))
* Added a `fade` argument to `modalDialog()` -- setting it to `FALSE` will remove the usual fade-in animation for that modal window. ([#1414](https://github.com/rstudio/shiny/pull/1414))
* Exported function to apply input handlers to input values. This can be used for testing Shiny applications. ([#1421](https://github.com/rstudio/shiny/pull/1421))
* Fixed a "duplicate binding" error that occurred in some edge cases involving `insertUI` and nested `uiOutput`. ([#1402](https://github.com/rstudio/shiny/pull/1402))
* Fixed [#1422](https://github.com/rstudio/shiny/issues/1422): When using the `shiny.trace` option, allow specifying to only log SEND or RECV messages, or both. (PR [#1428](https://github.com/rstudio/shiny/pull/1428))
* Fixed [#1419](https://github.com/rstudio/shiny/issues/1419): Allow overriding a JS custom message handler. (PR [#1445](https://github.com/rstudio/shiny/pull/1445))
* Added `exportTestValues()` function, which allows a test driver to query the session for values internal to an application's server function. This only has an effect if the `shiny.testmode` option is set to `TRUE`. ([#1436](https://github.com/rstudio/shiny/pull/1436))
### Bug fixes
* Fixed [#1427](https://github.com/rstudio/shiny/issues/1427): make sure that modals do not close incorrectly when an element inside them is triggered as hidden. ([#1430](https://github.com/rstudio/shiny/pull/1430))
@@ -21,6 +162,8 @@ shiny 0.14.1.9001
* `sliderInputBinding.setValue()` now sends a slider's value immediately, instead of waiting for the usual 250ms debounce delay. ([#1429](https://github.com/rstudio/shiny/pull/1429))
* Fixed a bug where, in versions of R before 3.2, Shiny applications could crash due to a bug in R's implementation of `list2env()`. ([#1446](https://github.com/rstudio/shiny/pull/1446))
shiny 0.14.1
============

13
R/app.R
View File

@@ -20,9 +20,11 @@
#' @param onStart A function that will be called before the app is actually run.
#' This is only needed for \code{shinyAppObj}, since in the \code{shinyAppDir}
#' case, a \code{global.R} file can be used for this purpose.
#' @param options Named options that should be passed to the `runApp` call. You
#' can also specify \code{width} and \code{height} parameters which provide a
#' hint to the embedding environment about the ideal height/width for the app.
#' @param options Named options that should be passed to the \code{runApp} call
#' (these can be any of the following: "port", "launch.browser", "host", "quiet",
#' "display.mode" and "test.mode"). You can also specify \code{width} and
#' \code{height} parameters which provide a hint to the embedding environment
#' about the ideal height/width for the app.
#' @param uiPattern A regular expression that will be applied to each \code{GET}
#' request to determine whether the \code{ui} should be used to handle the
#' request. Note that the entire request path must match the regular
@@ -39,6 +41,8 @@
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' shinyApp(
#' ui = fluidPage(
#' numericInput("n", "n", 1),
@@ -373,7 +377,8 @@ is.shiny.appobj <- function(x) {
print.shiny.appobj <- function(x, ...) {
opts <- x$options %OR% list()
opts <- opts[names(opts) %in%
c("port", "launch.browser", "host", "quiet", "display.mode")]
c("port", "launch.browser", "host", "quiet",
"display.mode", "test.mode")]
args <- c(list(x), opts)

View File

@@ -362,7 +362,7 @@ RestoreContext <- R6Class("RestoreContext",
self$input <- RestoreInputSet$new(inputs)
values <- valuesFromJSON(values)
self$values <- list2env(values, self$values)
self$values <- list2env2(values, self$values)
}
)
)
@@ -385,7 +385,7 @@ RestoreInputSet <- R6Class("RestoreInputSet",
public = list(
initialize = function(values) {
private$values <- list2env(values, parent = emptyenv())
private$values <- list2env2(values, parent = emptyenv())
},
exists = function(name) {
@@ -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

@@ -277,6 +277,7 @@ titlePanel <- function(title, windowTitle=title) {
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' # Define UI
#' ui <- fluidPage(
@@ -442,6 +443,7 @@ inputPanel <- function(...) {
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' # Server code used for all examples
#' server <- function(input, output) {

View File

@@ -342,10 +342,18 @@ navbarPage <- function(title,
tabs <- list(...)
tabset <- buildTabset(tabs, "nav navbar-nav", NULL, id, selected)
# function to return plain or fluid class name
className <- function(name) {
if (fluid)
paste(name, "-fluid", sep="")
else
name
}
# built the container div dynamically to support optional collapsibility
if (collapsible) {
navId <- paste("navbar-collapse-", p_randomInt(1000, 10000), sep="")
containerDiv <- div(class="container",
containerDiv <- div(class=className("container"),
div(class="navbar-header",
tags$button(type="button", class="navbar-toggle collapsed",
`data-toggle`="collapse", `data-target`=paste0("#", navId),
@@ -359,7 +367,7 @@ navbarPage <- function(title,
div(class="navbar-collapse collapse", id=navId, tabset$navList)
)
} else {
containerDiv <- div(class="container",
containerDiv <- div(class=className("container"),
div(class="navbar-header",
span(class="navbar-brand", pageTitle)
),
@@ -367,14 +375,6 @@ navbarPage <- function(title,
)
}
# function to return plain or fluid class name
className <- function(name) {
if (fluid)
paste(name, "-fluid", sep="")
else
name
}
# build the main tab content div
contentDiv <- div(class=className("container"))
if (!is.null(header))
@@ -935,21 +935,34 @@ textOutput <- function(outputId, container = if (inline) span else div, inline =
#' Render a reactive output variable as verbatim text within an
#' application page. The text will be included within an HTML \code{pre} tag.
#' @param outputId output variable to read the value from
#' @param placeholder if the output is empty or \code{NULL}, should an empty
#' rectangle be displayed to serve as a placeholder? (does not affect
#' behavior when the the output in nonempty)
#' @return A verbatim text output element that can be included in a panel
#' @details Text is HTML-escaped prior to rendering. This element is often used
#' with the \link{renderPrint} function to preserve fixed-width formatting
#' of printed objects.
#' with the \link{renderPrint} function to preserve fixed-width formatting
#' of printed objects.
#' @examples
#' mainPanel(
#' h4("Summary"),
#' verbatimTextOutput("summary"),
#'
#' h4("Observations"),
#' tableOutput("view")
#' )
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' shinyApp(
#' ui = basicPage(
#' textInput("txt", "Enter the text to display below:"),
#' verbatimTextOutput("default"),
#' verbatimTextOutput("placeholder", placeholder = TRUE)
#' ),
#' server = function(input, output) {
#' output$default <- renderText({ input$txt })
#' output$placeholder <- renderText({ input$txt })
#' }
#' )
#' }
#' @export
verbatimTextOutput <- function(outputId) {
textOutput(outputId, container = pre)
verbatimTextOutput <- function(outputId, placeholder = FALSE) {
pre(id = outputId,
class = paste(c("shiny-text-output", if (!placeholder) "noplaceholder"),
collapse = " ")
)
}
@@ -1123,7 +1136,7 @@ imageOutput <- function(outputId, width = "100%", height="400px",
#' same \code{id} to disappear.
#' @inheritParams textOutput
#' @note The arguments \code{clickId} and \code{hoverId} only work for R base
#' graphics (see the \pkg{\link{graphics}} package). They do not work for
#' graphics (see the \pkg{\link[graphics:graphics-package]{graphics}} package). They do not work for
#' \pkg{\link[grid:grid-package]{grid}}-based graphics, such as \pkg{ggplot2},
#' \pkg{lattice}, and so on.
#'
@@ -1421,6 +1434,7 @@ uiOutput <- htmlOutput
#' is assigned to.
#' @param label The label that should appear on the button.
#' @param class Additional CSS classes to apply to the tag, if any.
#' @param ... Other arguments to pass to the container tag function.
#'
#' @examples
#' \dontrun{
@@ -1439,27 +1453,29 @@ uiOutput <- htmlOutput
#' }
#'
#' @aliases downloadLink
#' @seealso downloadHandler
#' @seealso \code{\link{downloadHandler}}
#' @export
downloadButton <- function(outputId,
label="Download",
class=NULL) {
class=NULL, ...) {
aTag <- tags$a(id=outputId,
class=paste('btn btn-default shiny-download-link', class),
href='',
target='_blank',
download=NA,
icon("download"),
label)
label, ...)
}
#' @rdname downloadButton
#' @export
downloadLink <- function(outputId, label="Download", class=NULL) {
downloadLink <- function(outputId, label="Download", class=NULL, ...) {
tags$a(id=outputId,
class=paste(c('shiny-download-link', class), collapse=" "),
href='',
target='_blank',
label)
download=NA,
label, ...)
}
@@ -1527,7 +1543,7 @@ icon <- function(name, class = NULL, lib = "font-awesome") {
# font-awesome needs an additional dependency (glyphicon is in bootstrap)
if (lib == "font-awesome") {
htmlDependencies(iconTag) <- htmlDependency(
"font-awesome", "4.6.3", c(href="shared/font-awesome"),
"font-awesome", "4.7.0", c(href="shared/font-awesome"),
stylesheet = "css/font-awesome.min.css"
)
}

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

@@ -21,7 +21,7 @@
#' @param width Width in pixels.
#' @param height Height in pixels.
#' @param res Resolution in pixels per inch. This value is passed to
#' \code{\link{png}}. Note that this affects the resolution of PNG rendering in
#' \code{\link[grDevices]{png}}. Note that this affects the resolution of PNG rendering in
#' R; it won't change the actual ppi of the browser.
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
#' These can be used to set the width, height, background color, etc.

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,47 @@
#' 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) {
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

@@ -10,7 +10,7 @@
#' \item \code{yy} Year without century (12)
#' \item \code{yyyy} Year with century (2012)
#' \item \code{mm} Month number, with leading zero (01-12)
#' \item \code{m} Month number, without leading zero (01-12)
#' \item \code{m} Month number, without leading zero (1-12)
#' \item \code{M} Abbreviated month name
#' \item \code{MM} Full month name
#' \item \code{dd} Day of month with leading zero

View File

@@ -10,7 +10,7 @@
#' \item \code{yy} Year without century (12)
#' \item \code{yyyy} Year with century (2012)
#' \item \code{mm} Month number, with leading zero (01-12)
#' \item \code{m} Month number, without leading zero (01-12)
#' \item \code{m} Month number, without leading zero (1-12)
#' \item \code{M} Abbreviated month name
#' \item \code{MM} Full month name
#' \item \code{dd} Day of month with leading zero
@@ -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}.
#'
@@ -15,7 +15,12 @@
#'
#' @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.
#' This can also be a named list whose elements are (either named or
#' unnamed) lists or vectors. If this is the case, the outermost names
#' will be used as the "optgroup" label for the elements in the respective
#' sublist. This allows you to group and label similar choices. See the
#' example section for a small demo of this feature.
#' @param selected The initially selected value (or multiple values if
#' \code{multiple = TRUE}). If not specified then defaults to the first value
#' for single-select lists and no values for multiple select lists.
@@ -34,26 +39,43 @@
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#'
#' ui <- fluidPage(
#' selectInput("variable", "Variable:",
#' c("Cylinders" = "cyl",
#' "Transmission" = "am",
#' "Gears" = "gear")),
#' tableOutput("data")
#' # basic example
#' shinyApp(
#' ui = fluidPage(
#' selectInput("variable", "Variable:",
#' c("Cylinders" = "cyl",
#' "Transmission" = "am",
#' "Gears" = "gear")),
#' tableOutput("data")
#' ),
#' server = function(input, output) {
#' output$data <- renderTable({
#' mtcars[, c("mpg", input$variable), drop = FALSE]
#' }, rownames = TRUE)
#' }
#' )
#'
#' server <- function(input, output) {
#' output$data <- renderTable({
#' mtcars[, c("mpg", input$variable), drop = FALSE]
#' }, rownames = TRUE)
#' }
#'
#' shinyApp(ui, server)
#' # demoing optgroup support in the `choices` arg
#' shinyApp(
#' ui = fluidPage(
#' selectInput("state", "Choose a state:",
#' list(`East Coast` = c("NY", "NJ", "CT"),
#' `West Coast` = c("WA", "OR", "CA"),
#' `Midwest` = c("MN", "WI", "IA"))
#' ),
#' textOutput("result")
#' ),
#' server = function(input, output) {
#' output$result <- renderText({
#' paste("You chose", input$state)
#' })
#' }
#' )
#' }
#' @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)
@@ -63,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'.")
@@ -133,7 +155,7 @@ needOptgroup <- function(choices) {
#' @rdname selectInput
#' @param ... Arguments passed to \code{selectInput()}.
#' @param options A list of options. See the documentation of \pkg{selectize.js}
#' for possible options (character option values inside \code{\link{I}()} will
#' for possible options (character option values inside \code{\link[base]{I}()} will
#' be treated as literal JavaScript code; see \code{\link{renderDataTable}()}
#' for details).
#' @param width The width of the input, e.g. \code{'400px'}, or \code{'100\%'};

View File

@@ -36,7 +36,7 @@
#' format string, to be passed to the Javascript strftime library. See
#' \url{https://github.com/samsonjs/strftime} for more details. The allowed
#' format specifications are very similar, but not identical, to those for R's
#' \code{\link{strftime}} function. For Dates, the default is \code{"\%F"}
#' \code{\link[base]{strftime}} function. For Dates, the default is \code{"\%F"}
#' (like \code{"2015-07-01"}), and for POSIXt, the default is \code{"\%F \%T"}
#' (like \code{"2015-07-01 15:32:10"}).
#' @param timezone Only used if the values are POSIXt objects. A string
@@ -51,6 +51,7 @@
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' ui <- fluidPage(
#' sliderInput("obs", "Number of observations:",
@@ -163,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"
@@ -219,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

@@ -1,8 +1,27 @@
#' Create a submit button
#'
#' Create a submit button for an input form. Forms that include a submit
#' Create a submit button for an app. Apps that include a submit
#' button do not automatically update their outputs when inputs change,
#' rather they wait until the user explicitly clicks the submit button.
#' The use of \code{submitButton} is generally discouraged in favor of
#' the more versatile \code{\link{actionButton}} (see details below).
#'
#' Submit buttons are unusual Shiny inputs, and we recommend using
#' \code{\link{actionButton}} instead of \code{submitButton} when you
#' want to delay a reaction.
#' See \href{http://shiny.rstudio.com/articles/action-buttons.html}{this
#' article} for more information (including a demo of how to "translate"
#' code using a \code{submitButton} to code using an \code{actionButton}).
#'
#' In essence, the presence of a submit button stops all inputs from
#' sending their values automatically to the server. This means, for
#' instance, that if there are \emph{two} submit buttons in the same app,
#' clicking either one will cause all inputs in the app to send their
#' values to the server. This is probably not what you'd want, which is
#' why submit button are unwieldy for all but the simplest apps. There
#' are other problems with submit buttons: for example, dynamically
#' created submit buttons (for example, with \code{\link{renderUI}}
#' or \code{\link{insertUI}}) will not work.
#'
#' @param text Button caption
#' @param icon Optional \code{\link{icon}} to appear on the button
@@ -13,8 +32,26 @@
#' @family input elements
#'
#' @examples
#' submitButton("Update View")
#' submitButton("Update View", icon("refresh"))
#' if (interactive()) {
#'
#' shinyApp(
#' ui = basicPage(
#' numericInput("num", label = "Make changes", value = 1),
#' submitButton("Update View", icon("refresh")),
#' helpText("When you click the button above, you should see",
#' "the output below update to reflect the value you",
#' "entered at the top:"),
#' verbatimTextOutput("value")
#' ),
#' server = function(input, output) {
#'
#' # submit buttons do not have a value of their own,
#' # they control when the app accesses values of other widgets.
#' # input$num is the value of the number widget.
#' output$value <- renderPrint({ input$num })
#' }
#' )
#' }
#' @export
submitButton <- function(text = "Apply Changes", icon = NULL, width = NULL) {
div(

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(HTML(name)))
tags$label(class = paste0(type, "-inline"), inputTag,
tags$span(pd$html, pd$dep))
} else {
tags$div(class = type,
tags$label(inputTag, tags$span(HTML(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,12 +237,12 @@ 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
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' ui <- fluidPage(
#' plotOutput("plot")

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(
@@ -344,7 +566,7 @@ as.list.reactivevalues <- function(x, all.names=FALSE, ...) {
#' Convert a reactivevalues object to a list
#'
#' This function does something similar to what you might \code{\link{as.list}}
#' This function does something similar to what you might \code{\link[base]{as.list}}
#' to do. The difference is that the calling context will take dependencies on
#' every object in the reactivevalues object. To avoid taking dependencies on
#' all the objects, you can wrap the call with \code{\link{isolate}()}.
@@ -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,12 +747,17 @@ 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)
.mostRecentCtxId <<- ctx$id
ctx$onInvalidate(function() {
.invalidated <<- TRUE
.value <<- NULL # Value can be GC'd, it won't be read once invalidated
.dependents$invalidate()
})
.execCount <<- .execCount + 1L
@@ -628,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
@@ -642,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)
@@ -680,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)
@@ -1109,7 +1347,7 @@ setAutoflush <- local({
#' @return A no-parameter function that can be called from a reactive context,
#' in order to cause that context to be invalidated the next time the timer
#' interval elapses. Calling the returned function also happens to yield the
#' current time (as in \code{\link{Sys.time}}).
#' current time (as in \code{\link[base]{Sys.time}}).
#' @seealso \code{\link{invalidateLater}}
#'
#' @examples
@@ -1150,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
@@ -1417,7 +1659,7 @@ reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...)
#' The expression given to \code{isolate()} is evaluated in the calling
#' environment. This means that if you assign a variable inside the
#' \code{isolate()}, its value will be visible outside of the \code{isolate()}.
#' If you want to avoid this, you can use \code{\link{local}()} inside the
#' If you want to avoid this, you can use \code{\link[base]{local}()} inside the
#' \code{isolate()}.
#'
#' This function can also be useful for calling reactive expression at the
@@ -1530,6 +1772,8 @@ maskReactiveContext <- function(expr) {
#' invalidations that come from its reactive dependencies; it only invalidates
#' in response to the given event.
#'
#' @section ignoreNULL and ignoreInit:
#'
#' Both \code{observeEvent} and \code{eventReactive} take an \code{ignoreNULL}
#' parameter that affects behavior when the \code{eventExpr} evaluates to
#' \code{NULL} (or in the special case of an \code{\link{actionButton}},
@@ -1542,6 +1786,44 @@ maskReactiveContext <- function(expr) {
#' the action/calculation and just let the user re-initiate it (like a
#' "Recalculate" button).
#'
#' Unlike what happens for \code{ignoreNULL}, only \code{observeEvent} takes in an
#' \code{ignoreInit} argument. By default, \code{observeEvent} will run right when
#' it is created (except if, at that moment, \code{eventExpr} evaluates to \code{NULL}
#' and \code{ignoreNULL} is \code{TRUE}). But when responding to a click of an action
#' button, it may often be useful to set \code{ignoreInit} to \code{TRUE}. For
#' example, if you're setting up an \code{observeEvent} for a dynamically created
#' button, then \code{ignoreInit = TRUE} will guarantee that the action (in
#' \code{handlerExpr}) will only be triggered when the button is actually clicked,
#' instead of also being triggered when it is created/initialized.
#'
#' Even though \code{ignoreNULL} and \code{ignoreInit} can be used for similar
#' purposes they are independent from one another. Here's the result of combining
#' these:
#'
#' \describe{
#' \item{\code{ignoreNULL = TRUE} and \code{ignoreInit = FALSE}}{
#' This is the default. This combination means that \code{handlerExpr} will
#' run every time that \code{eventExpr} is not \code{NULL}. If, at the time
#' of the \code{observeEvent}'s creation, \code{handleExpr} happens to
#' \emph{not} be \code{NULL}, then the code runs.
#' }
#' \item{\code{ignoreNULL = FALSE} and \code{ignoreInit = FALSE}}{
#' This combination means that \code{handlerExpr} will run every time no
#' matter what.
#' }
#' \item{\code{ignoreNULL = FALSE} and \code{ignoreInit = TRUE}}{
#' This combination means that \code{handlerExpr} will \emph{not} run when
#' the \code{observeEvent} is created (because \code{ignoreInit = TRUE}),
#' but it will run every other time.
#' }
#' \item{\code{ignoreNULL = TRUE} and \code{ignoreInit = TRUE}}{
#' This combination means that \code{handlerExpr} will \emph{not} run when
#' the \code{observeEvent} is created (because \code{ignoreInit = TRUE}).
#' After that, \code{handlerExpr} will run every time that \code{eventExpr}
#' is not \code{NULL}.
#' }
#' }
#'
#' @param eventExpr A (quoted or unquoted) expression that represents the event;
#' this can be a simple reactive value like \code{input$click}, a call to a
#' reactive expression like \code{dataset()}, or even a complex expression
@@ -1583,6 +1865,15 @@ maskReactiveContext <- function(expr) {
#' @param ignoreNULL Whether the action should be triggered (or value
#' calculated, in the case of \code{eventReactive}) when the input is
#' \code{NULL}. See Details.
#' @param ignoreInit If \code{TRUE}, then, when this \code{observeEvent} is
#' first created/initialized, ignore the \code{handlerExpr} (the second
#' argument), whether it is otherwise supposed to run or not. The default is
#' \code{FALSE}. See Details.
#' @param once Whether this \code{observeEvent} should be immediately destroyed
#' after the first time that the code in \code{handlerExpr} is run. This
#' pattern is useful when you want to subscribe to a event that should only
#' happen once.
#'
#' @return \code{observeEvent} returns an observer reference class object (see
#' \code{\link{observe}}). \code{eventReactive} returns a reactive expression
#' object (see \code{\link{reactive}}).
@@ -1592,37 +1883,71 @@ maskReactiveContext <- function(expr) {
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' ui <- fluidPage(
#' column(4,
#' numericInput("x", "Value", 5),
#' br(),
#' actionButton("button", "Show")
#'
#' ## App 1: Sample usage
#' shinyApp(
#' ui = fluidPage(
#' column(4,
#' numericInput("x", "Value", 5),
#' br(),
#' actionButton("button", "Show")
#' ),
#' column(8, tableOutput("table"))
#' ),
#' column(8, tableOutput("table"))
#' server = function(input, output) {
#' # Take an action every time button is pressed;
#' # here, we just print a message to the console
#' observeEvent(input$button, {
#' cat("Showing", input$x, "rows\n")
#' })
#' # Take a reactive dependency on input$button, but
#' # not on any of the stuff inside the function
#' df <- eventReactive(input$button, {
#' head(cars, input$x)
#' })
#' output$table <- renderTable({
#' df()
#' })
#' }
#' )
#'
#' ## App 2: Using `once`
#' shinyApp(
#' ui = basicPage( actionButton("go", "Go")),
#' server = function(input, output, session) {
#' observeEvent(input$go, {
#' print(paste("This will only be printed once; all",
#' "subsequent button clicks won't do anything"))
#' }, once = TRUE)
#' }
#' )
#'
#' ## App 3: Using `ignoreInit` and `once`
#' shinyApp(
#' ui = basicPage(actionButton("go", "Go")),
#' server = function(input, output, session) {
#' observeEvent(input$go, {
#' insertUI("#go", "afterEnd",
#' actionButton("dynamic", "click to remove"))
#'
#' # set up an observer that depends on the dynamic
#' # input, so that it doesn't run when the input is
#' # created, and only runs once after that (since
#' # the side effect is remove the input from the DOM)
#' observeEvent(input$dynamic, {
#' removeUI("#dynamic")
#' }, ignoreInit = TRUE, once = TRUE)
#' })
#' }
#' )
#' server <- function(input, output) {
#' # Take an action every time button is pressed;
#' # here, we just print a message to the console
#' observeEvent(input$button, {
#' cat("Showing", input$x, "rows\n")
#' })
#' # Take a reactive dependency on input$button, but
#' # not on any of the stuff inside the function
#' df <- eventReactive(input$button, {
#' head(cars, input$x)
#' })
#' output$table <- renderTable({
#' df()
#' })
#' }
#' shinyApp(ui=ui, server=server)
#' }
#' @export
observeEvent <- function(eventExpr, handlerExpr,
event.env = parent.frame(), event.quoted = FALSE,
handler.env = parent.frame(), handler.quoted = FALSE,
label=NULL, suspended=FALSE, priority=0, domain=getDefaultReactiveDomain(),
autoDestroy = TRUE, ignoreNULL = TRUE) {
label = NULL, suspended = FALSE, priority = 0,
domain = getDefaultReactiveDomain(), autoDestroy = TRUE,
ignoreNULL = TRUE, ignoreInit = FALSE, once = FALSE) {
eventFunc <- exprToFunction(eventExpr, event.env, event.quoted)
if (is.null(label))
@@ -1632,16 +1957,29 @@ observeEvent <- function(eventExpr, handlerExpr,
handlerFunc <- exprToFunction(handlerExpr, handler.env, handler.quoted)
handlerFunc <- wrapFunctionLabel(handlerFunc, "observeEventHandler", ..stacktraceon = TRUE)
invisible(observe({
initialized <- FALSE
o <- observe({
e <- eventFunc()
if (ignoreInit && !initialized) {
initialized <<- TRUE
return()
}
if (ignoreNULL && isNullEvent(e)) {
return()
}
if (once) {
on.exit(o$destroy())
}
isolate(handlerFunc())
}, label = label, suspended = suspended, priority = priority, domain = domain,
autoDestroy = TRUE, ..stacktraceon = FALSE))
autoDestroy = TRUE, ..stacktraceon = FALSE)
invisible(o)
}
#' @rdname observeEvent
@@ -1649,8 +1987,8 @@ observeEvent <- function(eventExpr, handlerExpr,
eventReactive <- function(eventExpr, valueExpr,
event.env = parent.frame(), event.quoted = FALSE,
value.env = parent.frame(), value.quoted = FALSE,
label=NULL, domain=getDefaultReactiveDomain(),
ignoreNULL = TRUE) {
label = NULL, domain = getDefaultReactiveDomain(),
ignoreNULL = TRUE, ignoreInit = FALSE) {
eventFunc <- exprToFunction(eventExpr, event.env, event.quoted)
if (is.null(label))
@@ -1660,13 +1998,17 @@ eventReactive <- function(eventExpr, valueExpr,
handlerFunc <- exprToFunction(valueExpr, value.env, value.quoted)
handlerFunc <- wrapFunctionLabel(handlerFunc, "eventReactiveHandler", ..stacktraceon = TRUE)
initialized <- FALSE
invisible(reactive({
e <- eventFunc()
validate(need(
!ignoreNULL || !isNullEvent(e),
message = FALSE
))
if (ignoreInit && !initialized) {
initialized <<- TRUE
req(FALSE)
}
req(!ignoreNULL || !isNullEvent(e))
isolate(handlerFunc())
}, label = label, domain = domain, ..stacktraceon = FALSE))
@@ -1675,3 +2017,246 @@ eventReactive <- function(eventExpr, valueExpr,
isNullEvent <- function(value) {
is.null(value) || (inherits(value, 'shinyActionButtonValue') && value == 0)
}
#' Slow down a reactive expression with debounce/throttle
#'
#' Transforms a reactive expression by preventing its invalidation signals from
#' being sent unnecessarily often. This lets you ignore a very "chatty" reactive
#' expression until it becomes idle, which is useful when the intermediate
#' values don't matter as much as the final value, and the downstream
#' calculations that depend on the reactive expression take a long time.
#' \code{debounce} and \code{throttle} use different algorithms for slowing down
#' invalidation signals; see Details.
#'
#' @section Limitations:
#'
#' Because R is single threaded, we can't come close to guaranteeing that the
#' timing of debounce/throttle (or any other timing-related functions in
#' Shiny) will be consistent or accurate; at the time we want to emit an
#' invalidation signal, R may be performing a different task and we have no
#' way to interrupt it (nor would we necessarily want to if we could).
#' Therefore, it's best to think of the time windows you pass to these
#' functions as minimums.
#'
#' You may also see undesirable behavior if the amount of time spent doing
#' downstream processing for each change approaches or exceeds the time
#' window: in this case, debounce/throttle may not have any effect, as the
#' time each subsequent event is considered is already after the time window
#' has expired.
#'
#' @details
#'
#' This is not a true debounce/throttle in that it will not prevent \code{r}
#' from being called many times (in fact it may be called more times than
#' usual), but rather, the reactive invalidation signal that is produced by
#' \code{r} is debounced/throttled instead. Therefore, these functions should be
#' used when \code{r} is cheap but the things it will trigger (downstream
#' outputs and reactives) are expensive.
#'
#' Debouncing means that every invalidation from \code{r} will be held for the
#' specified time window. If \code{r} invalidates again within that time window,
#' then the timer starts over again. This means that as long as invalidations
#' continually arrive from \code{r} within the time window, the debounced
#' reactive will not invalidate at all. Only after the invalidations stop (or
#' slow down sufficiently) will the downstream invalidation be sent.
#'
#' \code{ooo-oo-oo---- => -----------o-}
#'
#' (In this graphical depiction, each character represents a unit of time, and
#' the time window is 3 characters.)
#'
#' Throttling, on the other hand, delays invalidation if the \emph{throttled}
#' reactive recently (within the time window) invalidated. New \code{r}
#' invalidations do not reset the time window. This means that if invalidations
#' continually come from \code{r} within the time window, the throttled reactive
#' will invalidate regularly, at a rate equal to or slower than than the time
#' window.
#'
#' \code{ooo-oo-oo---- => o--o--o--o---}
#'
#' @param r A reactive expression (that invalidates too often).
#' @param millis The debounce/throttle time window. You may optionally pass a
#' no-arg function or reactive expression instead, e.g. to let the end-user
#' control the time window.
#' @param priority Debounce/throttle is implemented under the hood using
#' \link[=observe]{observers}. Use this parameter to set the priority of
#' these observers. Generally, this should be higher than the priorities of
#' downstream observers and outputs (which default to zero).
#' @param domain See \link{domains}.
#'
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' library(shiny)
#' library(magrittr)
#'
#' ui <- fluidPage(
#' plotOutput("plot", click = clickOpts("hover")),
#' helpText("Quickly click on the plot above, while watching the result table below:"),
#' tableOutput("result")
#' )
#'
#' server <- function(input, output, session) {
#' hover <- reactive({
#' if (is.null(input$hover))
#' list(x = NA, y = NA)
#' else
#' input$hover
#' })
#' hover_d <- hover %>% debounce(1000)
#' hover_t <- hover %>% throttle(1000)
#'
#' output$plot <- renderPlot({
#' plot(cars)
#' })
#'
#' output$result <- renderTable({
#' data.frame(
#' mode = c("raw", "throttle", "debounce"),
#' x = c(hover()$x, hover_t()$x, hover_d()$x),
#' y = c(hover()$y, hover_t()$y, hover_d()$y)
#' )
#' })
#' }
#'
#' shinyApp(ui, server)
#' }
#'
#' @export
debounce <- function(r, millis, priority = 100, domain = getDefaultReactiveDomain()) {
# TODO: make a nice label for the observer(s)
force(r)
force(millis)
if (!is.function(millis)) {
origMillis <- millis
millis <- function() origMillis
}
v <- reactiveValues(
trigger = NULL,
when = NULL # the deadline for the timer to fire; NULL if not scheduled
)
# Responsible for tracking when f() changes.
firstRun <- TRUE
observe({
r()
if (firstRun) {
# During the first run we don't want to set v$when, as this will kick off
# the timer. We only want to do that when we see r() change.
firstRun <<- FALSE
return()
}
# The value (or possibly millis) changed. Start or reset the timer.
v$when <- Sys.time() + millis()/1000
}, label = "debounce tracker", domain = domain, priority = priority)
# This observer is the timer. It rests until v$when elapses, then touches
# v$trigger.
observe({
if (is.null(v$when))
return()
now <- Sys.time()
if (now >= v$when) {
# Mod by 999999999 to get predictable overflow behavior
v$trigger <- isolate(v$trigger %OR% 0) %% 999999999 + 1
v$when <- NULL
} else {
invalidateLater((v$when - now) * 1000)
}
}, label = "debounce timer", domain = domain, priority = priority)
# This is the actual reactive that is returned to the user. It returns the
# value of r(), but only invalidates/updates when v$trigger is touched.
er <- eventReactive(v$trigger, {
r()
}, label = "debounce result", ignoreNULL = FALSE, domain = domain)
# Force the value of er to be immediately cached upon creation. It's very hard
# to explain why this observer is needed, but if you want to understand, try
# commenting it out and studying the unit test failure that results.
primer <- observe({
primer$destroy()
er()
}, label = "debounce primer", domain = domain, priority = priority)
er
}
#' @rdname debounce
#' @export
throttle <- function(r, millis, priority = 100, domain = getDefaultReactiveDomain()) {
# TODO: make a nice label for the observer(s)
force(r)
force(millis)
if (!is.function(millis)) {
origMillis <- millis
millis <- function() origMillis
}
v <- reactiveValues(
trigger = 0,
lastTriggeredAt = NULL, # Last time we fired; NULL if never
pending = FALSE # If TRUE, trigger again when timer elapses
)
blackoutMillisLeft <- function() {
if (is.null(v$lastTriggeredAt)) {
0
} else {
max(0, (v$lastTriggeredAt + millis()/1000) - Sys.time()) * 1000
}
}
trigger <- function() {
v$lastTriggeredAt <- Sys.time()
# Mod by 999999999 to get predictable overflow behavior
v$trigger <- isolate(v$trigger) %% 999999999 + 1
v$pending <- FALSE
}
# Responsible for tracking when f() changes.
observeEvent(r(), {
if (v$pending) {
# In a blackout period and someone already scheduled; do nothing
} else if (blackoutMillisLeft() > 0) {
# In a blackout period but this is the first change in that period; set
# v$pending so that a trigger will be scheduled at the end of the period
v$pending <- TRUE
} else {
# Not in a blackout period. Trigger, which will start a new blackout
# period.
trigger()
}
}, label = "throttle tracker", ignoreNULL = FALSE, priority = priority, domain = domain)
observe({
if (!v$pending) {
return()
}
timeout <- blackoutMillisLeft()
if (timeout > 0) {
invalidateLater(timeout)
} else {
trigger()
}
}, priority = priority, domain = domain)
# This is the actual reactive that is returned to the user. It returns the
# value of r(), but only invalidates/updates when v$trigger is touched.
eventReactive(v$trigger, {
r()
}, label = "throttle result", ignoreNULL = FALSE, domain = domain)
}

View File

@@ -28,7 +28,7 @@
#' inline plot, you must provide numeric values (in pixels) to both
#' \code{width} and \code{height}.
#' @param res Resolution of resulting plot, in pixels per inch. This value is
#' passed to \code{\link{png}}. Note that this affects the resolution of PNG
#' passed to \code{\link[grDevices]{png}}. Note that this affects the resolution of PNG
#' rendering in R; it won't change the actual ppi of the browser.
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
#' These can be used to set the width, height, background color, etc.
@@ -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,189 @@ 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) {
# 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 <- ggplot2::summarise_layout(b)
coord <- ggplot2::summarise_coord(b)
layers <- 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 +622,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 +663,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 +737,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

@@ -73,7 +73,7 @@ removeInputHandler <- function(type){
# Apply input handler to a single input value
applyInputHandler <- function(name, val) {
applyInputHandler <- function(name, val, shinysession) {
splitName <- strsplit(name, ':')[[1]]
if (length(splitName) > 1) {
if (!inputHandlers$containsKey(splitName[[2]])) {
@@ -108,23 +108,14 @@ applyInputHandler <- function(name, val) {
#' output.
#'
#' @param inputs A named list of input values.
#' @param shinysession A Shiny session object.
#'
#' @seealso registerInputHandler
#'
#' @examples
#' applyInputHandlers(list(
#' "m1" = list(list(1, 2), list(3, 4)),
#' "m2:shiny.matrix" = list(list(1, 2), list(3, 4)),
#'
#' "d1" = "2016-01-01",
#' "d2:shiny.date" = "2016-01-01", # Date object
#'
#' "n1" = NULL,
#' "n2:shiny.number" = NULL # Converts to NA
#' ))
#' @export
applyInputHandlers <- function(inputs) {
inputs <- mapply(applyInputHandler, names(inputs), inputs, SIMPLIFY = FALSE)
#' @keywords internal
applyInputHandlers <- function(inputs, shinysession = getDefaultReactiveDomain()) {
inputs <- mapply(applyInputHandler, names(inputs), inputs,
MoreArgs = list(shinysession = shinysession),
SIMPLIFY = FALSE)
# Convert names like "button1:shiny.action" to "button1"
names(inputs) <- vapply(

View File

@@ -34,7 +34,7 @@ registerClient <- function(client) {
#' JavaScript/CSS files available to their components.
#'
#' @param prefix The URL prefix (without slashes). Valid characters are a-z,
#' A-Z, 0-9, hyphen, period, and underscore; and must begin with a-z or A-Z.
#' A-Z, 0-9, hyphen, period, and underscore.
#' For example, a value of 'foo' means that any request paths that begin with
#' '/foo' will be mapped to the given directory.
#' @param directoryPath The directory that contains the static resources to be
@@ -52,7 +52,7 @@ registerClient <- function(client) {
#' @export
addResourcePath <- function(prefix, directoryPath) {
prefix <- prefix[1]
if (!grepl('^[a-z][a-z0-9\\-_.]*$', prefix, ignore.case=TRUE, perl=TRUE)) {
if (!grepl('^[a-z0-9\\-_][a-z0-9\\-_.]*$', prefix, ignore.case=TRUE, perl=TRUE)) {
stop("addResourcePath called with invalid prefix; please see documentation")
}
@@ -218,7 +218,8 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
if (is.character(msg))
msg <- charToRaw(msg)
if (isTRUE(getOption('shiny.trace'))) {
traceOption <- getOption('shiny.trace', FALSE)
if (isTRUE(traceOption) || traceOption == "recv") {
if (binary)
message("RECV ", '$$binary data$$')
else
@@ -461,6 +462,9 @@ serviceApp <- function() {
.shinyServerMinVersion <- '0.3.4'
# Global flag that's TRUE whenever we're inside of the scope of a call to runApp
.globals$running <- FALSE
#' Run Shiny Application
#'
#' Runs a Shiny application. This function normally does not return; interrupt R
@@ -502,6 +506,9 @@ serviceApp <- function() {
#' application. If set to \code{"normal"}, displays the application normally.
#' Defaults to \code{"auto"}, which displays the application in the mode given
#' in its \code{DESCRIPTION} file, if any.
#' @param test.mode Should the application be launched in test mode? This is
#' only used for recording or running automated tests. Defaults to the
#' \code{shiny.testmode} option, or FALSE if the option is not set.
#'
#' @examples
#' \dontrun{
@@ -514,6 +521,8 @@ serviceApp <- function() {
#'
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' # Apps can be run without a server.r and ui.r file
#' runApp(list(
#' ui = bootstrapPage(
@@ -545,25 +554,73 @@ runApp <- function(appDir=getwd(),
interactive()),
host=getOption('shiny.host', '127.0.0.1'),
workerId="", quiet=FALSE,
display.mode=c("auto", "normal", "showcase")) {
display.mode=c("auto", "normal", "showcase"),
test.mode=getOption('shiny.testmode', FALSE)) {
on.exit({
handlerManager$clear()
}, add = TRUE)
if (.globals$running) {
stop("Can't call `runApp()` from within `runApp()`. If your ",
"application code contains `runApp()`, please remove it.")
}
.globals$running <- TRUE
on.exit({
.globals$running <- FALSE
}, add = TRUE)
# Enable per-app Shiny options
oldOptionSet <- .globals$options
on.exit({
.globals$options <- oldOptionSet
},add = TRUE)
if (is.null(host) || is.na(host))
host <- '0.0.0.0'
# Make warnings print immediately
# Set pool.scheduler to support pool package
ops <- options(warn = 1, pool.scheduler = scheduleTask)
on.exit(options(ops), add = TRUE)
appParts <- as.shiny.appobj(appDir)
# The lines below set some of the app's running options, which
# can be:
# - left unspeficied (in which case the arguments' default
# values from `runApp` kick in);
# - passed through `shinyApp`
# - passed through `runApp` (this function)
# - passed through both `shinyApp` and `runApp` (the latter
# takes precedence)
#
# Matrix of possibilities:
# | IN shinyApp | IN runApp | result | check |
# |-------------|-----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------|
# | no | no | use defaults | exhaust all possibilities: if it's missing (runApp does not specify); THEN if it's not in shinyApp appParts$options; THEN use defaults |
# | yes | no | use shinyApp | if it's missing (runApp does not specify); THEN if it's in shinyApp appParts$options; THEN use shinyApp |
# | no | yes | use runApp | if it's not missing (runApp specifies), use those |
# | yes | yes | use runApp | if it's not missing (runApp specifies), use those |
#
# I tried to make this as compact and intuitive as possible,
# given that there are four distinct possibilities to check
appOps <- appParts$options
findVal <- function(arg, default) {
if (arg %in% names(appOps)) appOps[[arg]] else default
}
if (missing(port))
port <- findVal("port", port)
if (missing(launch.browser))
launch.browser <- findVal("launch.browser", launch.browser)
if (missing(host))
host <- findVal("host", host)
if (missing(quiet))
quiet <- findVal("quiet", quiet)
if (missing(display.mode))
display.mode <- findVal("display.mode", display.mode)
if (missing(test.mode))
test.mode <- findVal("test.mode", test.mode)
if (is.null(host) || is.na(host)) host <- '0.0.0.0'
workerId(workerId)
if (inShinyServer()) {
@@ -583,6 +640,11 @@ runApp <- function(appDir=getwd(),
# the display.mode parameter. The latter takes precedence.
setShowcaseDefault(0)
.globals$testMode <- test.mode
if (test.mode) {
message("Running application in test mode.")
}
# If appDir specifies a path, and display mode is specified in the
# DESCRIPTION file at that path, apply it here.
if (is.character(appDir)) {
@@ -670,8 +732,6 @@ runApp <- function(appDir=getwd(),
}
}
appParts <- as.shiny.appobj(appDir)
# Extract appOptions (which is a list) and store them as shinyOptions, for
# this app. (This is the only place we have to store settings that are
# accessible both the UI and server portion of the app.)
@@ -715,12 +775,16 @@ runApp <- function(appDir=getwd(),
# Top-level ..stacktraceoff..; matches with ..stacktraceon in observe(),
# reactive(), Callbacks$invoke(), and others
..stacktraceoff..(
captureStackTraces(
captureStackTraces({
# If any observers were created before runApp was called, this will make
# sure they run once the app starts. (Issue #1013)
scheduleFlush()
while (!.globals$stopped) {
serviceApp()
Sys.sleep(0.001)
}
)
})
)
if (isTRUE(.globals$reterror)) {

314
R/shiny.R
View File

@@ -39,9 +39,12 @@ NULL
#' when an app is run. See \code{\link{runApp}} for more information.}
#' \item{shiny.port}{A port number that Shiny will listen on. See
#' \code{\link{runApp}} for more information.}
#' \item{shiny.trace}{If \code{TRUE}, all of the messages sent between the R
#' server and the web browser client will be printed on the console. This
#' is useful for debugging.}
#' \item{shiny.trace}{Print messages sent between the R server and the web
#' browser client to the R console. This is useful for debugging. Possible
#' values are \code{"send"} (only print messages sent to the client),
#' \code{"recv"} (only print messages received by the server), \code{TRUE}
#' (print all messages), or \code{FALSE} (default; don't print any of these
#' messages).}
#' \item{shiny.autoreload}{If \code{TRUE} when a Shiny app is launched, the
#' app directory will be continually monitored for changes to files that
#' have the extensions: r, htm, html, js, css, png, jpg, jpeg, gif. If any
@@ -104,6 +107,9 @@ NULL
#' particular error \code{e} to get displayed to the user, then set this option
#' to \code{TRUE} and use \code{stop(safeError(e))} for errors you want the
#' user to see.}
#' \item{shiny.testmode}{If \code{TRUE}, then enable features for testing Shiny
#' applications. If \code{FALSE} (the default), do not enable those features.
#' }
#' }
#' @name shiny-options
NULL
@@ -195,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.
@@ -270,6 +277,10 @@ workerId <- local({
#' This is the request that was used to initiate the websocket connection
#' (as opposed to the request that downloaded the web page for the app).
#' }
#' \item{userData}{
#' An environment for app authors and module/package authors to store whatever
#' session-specific data they want.
#' }
#' \item{resetBrush(brushId)}{
#' Resets/clears the brush with the given \code{brushId}, if it exists on
#' any \code{imageOutput} or \code{plotOutput} in the app.
@@ -323,6 +334,19 @@ workerId <- local({
#' \item{doBookmark()}{
#' Do bookmarking and invoke the onBookmark and onBookmarked callback functions.
#' }
#' \item{exportTestValues()}{
#' Registers expressions for export in test mode, available at the test
#' snapshot URL.
#' }
#' \item{getTestSnapshotUrl(input=TRUE, output=TRUE, export=TRUE,
#' format="json")}{
#' Returns a URL for the test snapshots. Only has an effect when the
#' \code{shiny.testmode} option is set to TRUE. For the input, output, and
#' export arguments, TRUE means to return all of these values. It is also
#' possible to specify by name which values to return by providing a
#' character vector, as in \code{input=c("x", "y")}. The format can be
#' "rds" or "json".
#' }
#'
#' @name session
NULL
@@ -351,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)
}
}
@@ -392,6 +428,12 @@ 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(),
outputValues = list(), # Saved output values (for testing mode)
testSnapshotUrl = character(0),
sendResponse = function(requestMsg, value) {
if (is.null(requestMsg$tag)) {
@@ -414,7 +456,8 @@ ShinySession <- R6Class(
if (self$closed){
return()
}
if (isTRUE(getOption('shiny.trace')))
traceOption <- getOption('shiny.trace', FALSE)
if (isTRUE(traceOption) || traceOption == "send")
message('SEND ',
gsub('(?m)base64,[a-zA-Z0-9+/=]+','[base64 data]',json,perl=TRUE))
private$websocket$send(json)
@@ -548,6 +591,127 @@ 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)
},
enableTestSnapshot = function() {
private$testSnapshotUrl <- self$registerDataObj("shinytest", NULL,
function(data, req) {
if (!isTRUE(private$testMode)) {
return()
}
params <- parseQueryString(req$QUERY_STRING)
# The format of the response that will be sent back. Defaults to
# "json" unless requested otherwise. The only other valid value is
# "rds".
format <- params$format %OR% "json"
values <- list()
if (!is.null(params$input)) {
allInputs <- isolate(
reactiveValuesToList(self$input, all.names = TRUE)
)
# If params$input is "1", return all; otherwise return just the
# inputs that are named in params$input, like "x,y,z".
if (params$input == "1") {
values$input <- allInputs
} else {
items <- strsplit(params$input, ",")[[1]]
items <- intersect(items, names(allInputs))
values$input <- allInputs[items]
}
values$input <- sortByName(values$input)
}
if (!is.null(params$output)) {
if (params$output == "1") {
values$output <- private$outputValues
} else {
items <- strsplit(params$output, ",")[[1]]
items <- intersect(items, names(private$outputValues))
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)
}
if (!is.null(params$export)) {
if (params$export == "1") {
values$export <- isolate(
lapply(private$testExportExprs, function(item) {
eval(item$expr, envir = item$env)
})
)
} else {
items <- strsplit(params$export, ",")[[1]]
items <- intersect(items, names(private$testExportExprs))
values$export <- isolate(
lapply(private$testExportExprs[items], function(item) {
eval(item$expr, envir = item$env)
})
)
}
values$export <- sortByName(values$export)
}
# Make sure input, output, and export are all named lists (at this
# point, they could be unnamed if they are empty lists). This is so
# that the resulting object is represented as an object in JSON
# instead of an array, and so that the RDS data structure is of a
# consistent type.
values <- lapply(values, asNamedVector)
if (length(values) == 0) {
return(httpResponse(400, "text/plain",
"None of export, input, or output requested."
))
}
if (identical(format, "json")) {
content <- toJSON(values, pretty = TRUE)
httpResponse(200, "application/json", content)
} else if (identical(format, "rds")) {
tmpfile <- tempfile("shinytest", fileext = ".rds")
saveRDS(values, tmpfile)
on.exit(unlink(tmpfile))
content <- readBin(tmpfile, "raw", n = file.info(tmpfile)$size)
httpResponse(200, "application/octet-stream", content)
} else {
httpResponse(400, "text/plain", paste("Invalid format requested:", format))
}
}
)
}
),
public = list(
@@ -562,6 +726,7 @@ ShinySession <- R6Class(
closed = logical(0),
request = 'ANY', # Websocket request object
singletons = character(0), # Tracks singleton HTML fragments sent to the page
userData = 'environment',
user = NULL,
groups = NULL,
@@ -582,6 +747,7 @@ ShinySession <- R6Class(
self$progressStack <- Stack$new()
self$files <- Map$new()
self$downloads <- Map$new()
self$userData <- new.env(parent = emptyenv())
self$input <- .createReactiveValues(private$.input, readonly=TRUE)
.setLabel(self$input, 'input')
@@ -600,6 +766,9 @@ ShinySession <- R6Class(
private$restoredCallbacks <- Callbacks$new()
private$createBookmarkObservers()
private$testMode <- .globals$testMode
private$enableTestSnapshot()
private$registerSessionEndCallbacks()
if (!is.null(websocket$request$HTTP_SHINY_SERVER_CREDENTIALS)) {
@@ -618,7 +787,8 @@ ShinySession <- R6Class(
private$sendMessage(
config = list(
workerId = workerId(),
sessionId = self$token
sessionId = self$token,
user = self$user
)
)
},
@@ -675,6 +845,24 @@ ShinySession <- R6Class(
stop("`fun` must be a function that takes one argument")
}
restoredCallbacks$register(fun)
},
exportTestValues = function(..., quoted_ = FALSE, env_ = parent.frame()) {
if (quoted_) {
dots <- list(...)
} else {
dots <- eval(substitute(alist(...)))
}
if (anyUnnamed(dots))
stop("exportTestValues: all arguments must be named.")
names(dots) <- ns(names(dots))
do.call(
.subset2(self, "exportTestValues"),
c(dots, quoted_ = TRUE, env_ = env_),
quote = TRUE
)
}
)
@@ -782,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) {
@@ -865,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
@@ -894,17 +1092,17 @@ ShinySession <- R6Class(
shinyCallingHandlers(func()),
shiny.custom.error = function(cond) {
if (isTRUE(getOption("show.error.messages"))) printError(cond)
structure(NULL, class = "try-error", condition = cond)
structure(list(), class = "try-error", condition = cond)
},
shiny.output.cancel = function(cond) {
structure(NULL, class = "cancel-output")
structure(list(), class = "cancel-output")
},
shiny.silent.error = function(cond) {
# Don't let shiny.silent.error go through the normal stop
# path of try, because we don't want it to print. But we
# do want to try to return the same looking result so that
# the code below can send the error to the browser.
structure(NULL, class = "try-error", condition = cond)
structure(list(), class = "try-error", condition = cond)
},
error = function(cond) {
if (isTRUE(getOption("show.error.messages"))) printError(cond)
@@ -913,7 +1111,7 @@ ShinySession <- R6Class(
"logs or contact the app author for",
"clarification."))
}
invisible(structure(NULL, class = "try-error", condition = cond))
invisible(structure(list(), class = "try-error", condition = cond))
},
finally = {
private$sendMessage(recalculating = list(
@@ -942,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)
})
@@ -992,16 +1196,20 @@ ShinySession <- R6Class(
}
private$progressKeys <- character(0)
values <- private$invalidatedOutputValues
values <- as.list(private$invalidatedOutputValues)
private$invalidatedOutputValues <- Map$new()
errors <- private$invalidatedOutputErrors
errors <- as.list(private$invalidatedOutputErrors)
private$invalidatedOutputErrors <- Map$new()
inputMessages <- private$inputMessageQueue
private$inputMessageQueue <- list()
if (isTRUE(private$testMode)) {
private$storeOutputValues(mergeVectors(values, errors))
}
private$sendMessage(
errors = as.list(errors),
values = as.list(values),
errors = errors,
values = values,
inputMessages = inputMessages
)
},
@@ -1099,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")
@@ -1174,6 +1386,45 @@ ShinySession <- R6Class(
)
},
exportTestValues = function(..., quoted_ = FALSE, env_ = parent.frame()) {
# Get a named list of unevaluated expressions.
if (quoted_) {
dots <- list(...)
} else {
dots <- eval(substitute(alist(...)))
}
if (anyUnnamed(dots))
stop("exportTestValues: all arguments must be named.")
# Create a named list where each item is a list with an expression and
# environment in which to eval the expression.
items <- lapply(dots, function(expr) {
list(expr = expr, env = env_)
})
private$testExportExprs <- mergeVectors(private$testExportExprs, items)
},
getTestSnapshotUrl = function(input = TRUE, output = TRUE, export = TRUE,
format = "json") {
reqString <- function(group, value) {
if (isTRUE(value))
paste0(group, "=1")
else if (is.character(value))
paste0(group, "=", paste(value, collapse = ","))
else
""
}
paste(
private$testSnapshotUrl,
reqString("input", input),
reqString("output", output),
reqString("export", export),
paste0("format=", format),
sep = "&"
)
},
reactlog = function(logEntry) {
# Use sendCustomMessage instead of sendMessage, because the handler in
@@ -1202,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(
@@ -1610,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

@@ -24,7 +24,7 @@ withMathJax <- function(...) {
)
}
renderPage <- function(ui, connection, showcase=0) {
renderPage <- function(ui, connection, showcase=0, testMode=FALSE) {
# If the ui is a NOT complete document (created by htmlTemplate()), then do some
# preprocessing and make sure it's a complete document.
if (!inherits(ui, "html_document")) {
@@ -45,11 +45,18 @@ renderPage <- function(ui, connection, showcase=0) {
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")
)
if (testMode) {
# Add code injection listener if in test mode
shiny_deps[[length(shiny_deps) + 1]] <-
htmlDependency("shiny-testmode", utils::packageVersion("shiny"),
c(href="shared"), script = "shiny-testmode.js")
}
html <- renderDocument(ui, shiny_deps, processDep = createWebDependency)
writeUTF8(html, con = connection)
}
@@ -91,6 +98,8 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") {
showcaseMode <- mode
}
testMode <- .globals$testMode %OR% FALSE
# Create a restore context using query string
bookmarkStore <- getShinyOption("bookmarkStore", default = "disable")
if (bookmarkStore == "disable") {
@@ -121,7 +130,7 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") {
if (is.null(uiValue))
return(NULL)
renderPage(uiValue, textConn, showcaseMode)
renderPage(uiValue, textConn, showcaseMode, testMode)
html <- paste(readLines(textConn, encoding = 'UTF-8'), collapse='\n')
return(httpResponse(200, content=enc2utf8(html)))
}

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}
@@ -127,6 +147,7 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' ui <- fluidPage(
#' sliderInput("n", "Number of observations", 2, 1000, 500),
@@ -220,7 +241,7 @@ renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
#'
#' Makes a reactive version of the given function that captures any printed
#' output, and also captures its printable result (unless
#' \code{\link{invisible}}), into a string. The resulting function is suitable
#' \code{\link[base]{invisible}}), into a string. The resulting function is suitable
#' for assigning to an \code{output} slot.
#'
#' The corresponding HTML output tag can be anything (though \code{pre} is
@@ -232,14 +253,14 @@ renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
#'
#' Note that unlike most other Shiny output functions, if the given function
#' returns \code{NULL} then \code{NULL} will actually be visible in the output.
#' To display nothing, make your function return \code{\link{invisible}()}.
#' To display nothing, make your function return \code{\link[base]{invisible}()}.
#'
#' @param expr An expression that may print output and/or return a printable R
#' object.
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#' @param width The value for \code{\link{options}('width')}.
#' @param width The value for \code{\link[base]{options}('width')}.
#' @param outputArgs A list of arguments to be passed through to the implicit
#' call to \code{\link{verbatimTextOutput}} when \code{renderPrint} is used
#' in an interactive R Markdown document.
@@ -409,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
@@ -420,7 +443,7 @@ downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()
#' the server infrastructure.
#'
#' For the \code{options} argument, the character elements that have the class
#' \code{"AsIs"} (usually returned from \code{\link{I}()}) will be evaluated in
#' \code{"AsIs"} (usually returned from \code{\link[base]{I}()}) will be evaluated in
#' JavaScript. This is useful when the type of the option value is not supported
#' in JSON, e.g., a JavaScript function, which can be obtained by evaluating a
#' character string. Note this only applies to the root-level elements of the

61
R/test-export.R Normal file
View File

@@ -0,0 +1,61 @@
#' Register expressions for export in test mode
#'
#' This function registers expressions that will be evaluated when a test export
#' event occurs. These events are triggered by accessing a snapshot URL.
#'
#' This function only has an effect if the app is launched in test mode. This is
#' done by calling \code{runApp()} with \code{test.mode=TRUE}, or by setting the
#' global option \code{shiny.testmode} to \code{TRUE}.
#'
#' @param quoted_ Are the expression quoted? Default is \code{FALSE}.
#' @param env_ The environment in which the expression should be evaluated.
#' @param session_ A Shiny session object.
#' @param ... Named arguments that are quoted or unquoted expressions that will
#' be captured and evaluated when snapshot URL is visited.
#' @examples
#' ## Only run this example in interactive R sessions
#' if (interactive()) {
#'
#' options(shiny.testmode = TRUE)
#'
#' # This application shows the test snapshot URL; clicking on it will
#' # fetch the input, output, and exported values in JSON format.
#' shinyApp(
#' ui = basicPage(
#' h4("Snapshot URL: "),
#' uiOutput("url"),
#' h4("Current values:"),
#' verbatimTextOutput("values"),
#' actionButton("inc", "Increment x")
#' ),
#'
#' server = function(input, output, session) {
#' vals <- reactiveValues(x = 1)
#' y <- reactive({ vals$x + 1 })
#'
#' observeEvent(input$inc, {
#' vals$x <<- vals$x + 1
#' })
#'
#' exportTestValues(
#' x = vals$x,
#' y = y()
#' )
#'
#' output$url <- renderUI({
#' url <- session$getTestSnapshotUrl(format="json")
#' a(href = url, url)
#' })
#'
#' output$values <- renderText({
#' paste0("vals$x: ", vals$x, "\ny: ", y())
#' })
#' }
#' )
#' }
#' @export
exportTestValues <- function(..., quoted_ = FALSE, env_ = parent.frame(),
session_ = getDefaultReactiveDomain())
{
session_$exportTestValues(..., quoted_ = quoted_, env_ = env_)
}

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)
@@ -622,7 +629,7 @@ updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
res <- checkAsIs(options)
cfg <- tags$script(
type = 'application/json',
`data-for` = inputId,
`data-for` = session$ns(inputId),
`data-eval` = if (length(res$eval)) HTML(toJSON(res$eval)),
HTML(toJSON(res$options))
)

View File

@@ -182,6 +182,16 @@ anyUnnamed <- function(x) {
any(!nzchar(nms))
}
# Given a vector/list, returns a named vector (the labels will be blank).
asNamedVector <- function(x) {
if (!is.null(names(x)))
return(x)
names(x) <- rep.int("", length(x))
x
}
# Given two named vectors, join them together, and keep only the last element
# with a given name in the resulting vector. If b has any elements with the same
# name as elements in a, the element in a is dropped. Also, if there are any
@@ -196,6 +206,30 @@ mergeVectors <- function(a, b) {
x[!drop_idx]
}
# Sort a vector by the names of items. If there are multiple items with the
# same name, preserve the original order of those items. For empty
# vectors/lists/NULL, return the original value.
sortByName <- function(x) {
if (anyUnnamed(x))
stop("All items must be named")
# Special case for empty vectors/lists, and NULL
if (length(x) == 0)
return(x)
x[order(names(x))]
}
# Wrapper around list2env with a NULL check. In R <3.2.0, if an empty unnamed
# list is passed to list2env(), it errors. But an empty named list is OK. For
# R >=3.2.0, this wrapper is not necessary.
list2env2 <- function(x, ...) {
# Ensure that zero-length lists have a name attribute
if (length(x) == 0)
attr(x, "names") <- character(0)
list2env(x, ...)
}
# Combine dir and (file)name into a file path. If a file already exists with a
# name differing only by case, then use it instead.
@@ -1094,6 +1128,7 @@ reactiveStop <- function(message = "", class = NULL) {
#' @examples
#' ## Only run examples in interactive R sessions
#' if (interactive()) {
#' options(device.ask.default = FALSE)
#'
#' ui <- fluidPage(
#' checkboxGroupInput('in1', 'Check some letters', choices = head(LETTERS)),
@@ -1196,7 +1231,7 @@ need <- function(expr, message = paste(label, "must be provided"), label) {
#' \strong{Truthy and falsy values}
#'
#' The terms "truthy" and "falsy" generally indicate whether a value, when
#' coerced to a \code{\link{logical}}, is \code{TRUE} or \code{FALSE}. We use
#' coerced to a \code{\link[base]{logical}}, is \code{TRUE} or \code{FALSE}. We use
#' the term a little loosely here; our usage tries to match the intuitive
#' notions of "Is this value missing or available?", or "Has the user provided
#' an answer?", or in the case of action buttons, "Has the button been

View File

@@ -59,6 +59,10 @@ devtools::install_version("shiny", version = "0.10.2.2")
The Javascript code in Shiny is minified using tools that run on Node.js. See the tools/ directory for more information.
## Guidelines for contributing
We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.md](CONTRIBUTING.md) file for detailed guidelines of how to contribute.
## License
The shiny package is licensed under the GPLv3. See these files in the inst directory for additional details:

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

@@ -1 +1 @@
This example demonstrates some additional widgets included in Shiny, such as `helpText` and `submitButton`. The latter is used to delay rendering output until the user explicitly requests it.
This example demonstrates some additional widgets included in Shiny, such as `helpText` and `actionButton`. The latter is used to delay rendering output until the user explicitly requests it (a construct which also introduces two important server functions, `eventReactive` and `isolate`).

View File

@@ -1,26 +1,32 @@
library(shiny)
library(datasets)
# Define server logic required to summarize and view the
# Define server logic required to summarize and view the
# selected dataset
function(input, output) {
# Return the requested dataset
datasetInput <- reactive({
# Return the requested dataset. Note that we use `eventReactive()`
# here, which takes a dependency on input$update (the action
# button), so that the output is only updated when the user
# clicks the button.
datasetInput <- eventReactive(input$update, {
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
}, ignoreNULL = FALSE)
# Generate a summary of the dataset
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
# Show the first "n" observations
# Show the first "n" observations. The use of `isolate()` here
# is necessary because we don't want the table to update
# whenever input$obs changes (only when the user clicks the
# action button).
output$view <- renderTable({
head(datasetInput(), n = input$obs)
head(datasetInput(), n = isolate(input$obs))
})
}

View File

@@ -2,32 +2,32 @@ library(shiny)
# Define UI for dataset viewer application
fluidPage(
# Application title.
titlePanel("More Widgets"),
# Sidebar with controls to select a dataset and specify the
# number of observations to view. The helpText function is
# also used to include clarifying text. Most notably, the
# inclusion of a submitButton defers the rendering of output
# inclusion of an actionButton defers the rendering of output
# until the user explicitly clicks the button (rather than
# doing it immediately when inputs change). This is useful if
# the computations required to render output are inordinately
# time-consuming.
sidebarLayout(
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10),
helpText("Note: while the data view will show only the specified",
"number of observations, the summary will still be based",
"on the full dataset."),
submitButton("Update View")
actionButton("update", "Update View")
),
# Show a summary of the dataset and an HTML table with the
# requested number of observations. Note the use of the h4
# function to provide an additional header above each output
@@ -35,7 +35,7 @@ fluidPage(
mainPanel(
h4("Summary"),
verbatimTextOutput("summary"),
h4("Observations"),
tableOutput("view")
)

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",
@@ -115,24 +116,26 @@ sd_section("Rendering functions",
"reactiveUI"
)
)
sd_section("Reactive constructs",
sd_section("Reactive programming",
"A sub-library that provides reactive programming facilities for R.",
c(
"invalidateLater",
"is.reactivevalues",
"isolate",
"makeReactiveBinding",
"reactive",
"observe",
"observeEvent",
"reactive",
"reactiveVal",
"reactiveValues",
"reactiveValuesToList",
"is.reactivevalues",
"isolate",
"invalidateLater",
"debounce",
"showReactLog",
"makeReactiveBinding",
"reactiveFileReader",
"reactivePoll",
"reactiveTimer",
"reactiveValues",
"reactiveValuesToList",
"freezeReactiveValue",
"domains",
"showReactLog"
"freezeReactiveValue"
)
)
sd_section("Boilerplate",
@@ -189,6 +192,9 @@ sd_section("Utility functions",
"installExprFunction",
"parseQueryString",
"plotPNG",
"exportTestValues",
"snapshotExclude",
"markOutputAttrs",
"repeatable",
"shinyDeprecated",
"serverInfo",

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,13 @@
/*!
* Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
/* FONT PATH
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
src: url('../fonts/fontawesome-webfont.eot?v=4.6.3');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');
src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -1832,6 +1832,7 @@
content: "\f23e";
}
.fa-battery-4:before,
.fa-battery:before,
.fa-battery-full:before {
content: "\f240";
}
@@ -2178,6 +2179,143 @@
.fa-font-awesome:before {
content: "\f2b4";
}
.fa-handshake-o:before {
content: "\f2b5";
}
.fa-envelope-open:before {
content: "\f2b6";
}
.fa-envelope-open-o:before {
content: "\f2b7";
}
.fa-linode:before {
content: "\f2b8";
}
.fa-address-book:before {
content: "\f2b9";
}
.fa-address-book-o:before {
content: "\f2ba";
}
.fa-vcard:before,
.fa-address-card:before {
content: "\f2bb";
}
.fa-vcard-o:before,
.fa-address-card-o:before {
content: "\f2bc";
}
.fa-user-circle:before {
content: "\f2bd";
}
.fa-user-circle-o:before {
content: "\f2be";
}
.fa-user-o:before {
content: "\f2c0";
}
.fa-id-badge:before {
content: "\f2c1";
}
.fa-drivers-license:before,
.fa-id-card:before {
content: "\f2c2";
}
.fa-drivers-license-o:before,
.fa-id-card-o:before {
content: "\f2c3";
}
.fa-quora:before {
content: "\f2c4";
}
.fa-free-code-camp:before {
content: "\f2c5";
}
.fa-telegram:before {
content: "\f2c6";
}
.fa-thermometer-4:before,
.fa-thermometer:before,
.fa-thermometer-full:before {
content: "\f2c7";
}
.fa-thermometer-3:before,
.fa-thermometer-three-quarters:before {
content: "\f2c8";
}
.fa-thermometer-2:before,
.fa-thermometer-half:before {
content: "\f2c9";
}
.fa-thermometer-1:before,
.fa-thermometer-quarter:before {
content: "\f2ca";
}
.fa-thermometer-0:before,
.fa-thermometer-empty:before {
content: "\f2cb";
}
.fa-shower:before {
content: "\f2cc";
}
.fa-bathtub:before,
.fa-s15:before,
.fa-bath:before {
content: "\f2cd";
}
.fa-podcast:before {
content: "\f2ce";
}
.fa-window-maximize:before {
content: "\f2d0";
}
.fa-window-minimize:before {
content: "\f2d1";
}
.fa-window-restore:before {
content: "\f2d2";
}
.fa-times-rectangle:before,
.fa-window-close:before {
content: "\f2d3";
}
.fa-times-rectangle-o:before,
.fa-window-close-o:before {
content: "\f2d4";
}
.fa-bandcamp:before {
content: "\f2d5";
}
.fa-grav:before {
content: "\f2d6";
}
.fa-etsy:before {
content: "\f2d7";
}
.fa-imdb:before {
content: "\f2d8";
}
.fa-ravelry:before {
content: "\f2d9";
}
.fa-eercast:before {
content: "\f2da";
}
.fa-microchip:before {
content: "\f2db";
}
.fa-snowflake-o:before {
content: "\f2dc";
}
.fa-superpowers:before {
content: "\f2dd";
}
.fa-wpexplorer:before {
content: "\f2de";
}
.fa-meetup:before {
content: "\f2e0";
}
.sr-only {
position: absolute;
width: 1px;

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 382 KiB

After

Width:  |  Height:  |  Size: 434 KiB

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

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

@@ -0,0 +1,8 @@
// Listen for messages from parent frame. This file is only added when the
// shiny.testmode option is TRUE.
window.addEventListener("message", function(e) {
var message = e.data;
if (message.code)
eval(message.code);
});

View File

@@ -1,3 +1,17 @@
/* This is necessary so that an empty verbatimTextOutput slot
is the same height as a non-empty one (only important when
* placeholder = TRUE) */
pre.shiny-text-output:empty::before {
content: " ";
}
pre.shiny-text-output.noplaceholder:empty {
margin: 0;
padding: 0;
border-width: 0;
height: 0;
}
#shiny-disconnected-overlay {
position: fixed;
top: 0;
@@ -81,6 +95,13 @@
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);
@@ -675,7 +786,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// function to normalize hostnames
var normalize = function normalize(hostname) {
if (hostname == "127.0.0.1") return "localhost";else return hostname;
if (hostname === "127.0.0.1") return "localhost";else return hostname;
};
// Send a 'disconnected' message to parent if we are on the same domin
@@ -686,7 +797,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
a.href = parentUrl;
// post the disconnected message if the hostnames are the same
if (normalize(a.hostname) == normalize(window.location.hostname)) {
if (normalize(a.hostname) === normalize(window.location.hostname)) {
var protocol = a.protocol.replace(':', ''); // browser compatability
var origin = protocol + '://' + a.hostname;
if (a.port) origin = origin + ':' + a.port;
@@ -971,8 +1082,14 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// Adds custom message handler - this one is exposed to the user
function addCustomMessageHandler(type, handler) {
// Remove any previously defined handlers so that only the most recent one
// will be called
if (customMessageHandlers[type]) {
throw 'handler for message of type "' + type + '" already added.';
var typeIdx = customMessageHandlerOrder.indexOf(type);
if (typeIdx !== -1) {
customMessageHandlerOrder.splice(typeIdx, 1);
delete customMessageHandlers[type];
}
}
if (typeof handler !== 'function') {
throw 'handler must be a function.';
@@ -1122,7 +1239,8 @@ 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;
});
addMessageHandler('busy', function (message) {
@@ -1154,7 +1272,8 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// render the HTML and deps to a null target, so
// the side-effect of rendering the deps, singletons,
// and <head> still occur
exports.renderHtml($([]), message.content.html, message.content.deps);
console.warn('The selector you chose ("' + message.selector + '") could not be found in the DOM.');
exports.renderHtml(message.content.html, $([]), message.content.deps);
} else {
targets.each(function (i, target) {
exports.renderContent(target, message.content, message.where);
@@ -1176,7 +1295,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) {
@@ -1253,13 +1411,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.
@@ -1271,13 +1425,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();
@@ -1305,6 +1455,26 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
};
exports.progressHandlers = progressHandlers;
// Returns a URL which can be queried to get values from inside the server
// function. This is enabled with `options(shiny.testmode=TRUE)`.
this.getTestSnapshotBaseUrl = function () {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _ref$fullUrl = _ref.fullUrl;
var fullUrl = _ref$fullUrl === undefined ? true : _ref$fullUrl;
var loc = window.location;
var url = "";
if (fullUrl) {
// Strip off everything after last slash in path, like dirname() in R
url = loc.origin + loc.pathname.replace(/\/[^/]*$/, "");
}
url += "/session/" + encodeURIComponent(this.config.sessionId) + "/dataobj/shinytest?w=" + encodeURIComponent(this.config.workerId) + "&nonce=" + randomId();
return url;
};
}).call(ShinyApp.prototype);
exports.showReconnectDialog = function () {
@@ -1361,22 +1531,22 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var fadeDuration = 250;
function show() {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _ref$html = _ref.html;
var html = _ref$html === undefined ? '' : _ref$html;
var _ref$action = _ref.action;
var action = _ref$action === undefined ? '' : _ref$action;
var _ref$deps = _ref.deps;
var deps = _ref$deps === undefined ? [] : _ref$deps;
var _ref$duration = _ref.duration;
var duration = _ref$duration === undefined ? 5000 : _ref$duration;
var _ref$id = _ref.id;
var id = _ref$id === undefined ? null : _ref$id;
var _ref$closeButton = _ref.closeButton;
var closeButton = _ref$closeButton === undefined ? true : _ref$closeButton;
var _ref$type = _ref.type;
var type = _ref$type === undefined ? null : _ref$type;
var _ref2$html = _ref2.html;
var html = _ref2$html === undefined ? '' : _ref2$html;
var _ref2$action = _ref2.action;
var action = _ref2$action === undefined ? '' : _ref2$action;
var _ref2$deps = _ref2.deps;
var deps = _ref2$deps === undefined ? [] : _ref2$deps;
var _ref2$duration = _ref2.duration;
var duration = _ref2$duration === undefined ? 5000 : _ref2$duration;
var _ref2$id = _ref2.id;
var id = _ref2$id === undefined ? null : _ref2$id;
var _ref2$closeButton = _ref2.closeButton;
var closeButton = _ref2$closeButton === undefined ? true : _ref2$closeButton;
var _ref2$type = _ref2.type;
var type = _ref2$type === undefined ? null : _ref2$type;
if (!id) id = randomId();
@@ -1520,12 +1690,12 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// content is non-Bootstrap. Bootstrap modals require some special handling,
// which is coded in here.
show: function show() {
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _ref2$html = _ref2.html;
var html = _ref2$html === undefined ? '' : _ref2$html;
var _ref2$deps = _ref2.deps;
var deps = _ref2$deps === undefined ? [] : _ref2$deps;
var _ref3$html = _ref3.html;
var html = _ref3$html === undefined ? '' : _ref3$html;
var _ref3$deps = _ref3.deps;
var deps = _ref3$deps === undefined ? [] : _ref3$deps;
// If there was an existing Bootstrap modal, then there will be a modal-
@@ -1549,6 +1719,20 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
});
}
$modal.on('keydown.shinymodal', function (e) {
// If we're listening for Esc, don't let the event propagate. See
// https://github.com/rstudio/shiny/issues/1453. The value of
// data("keyboard") needs to be checked inside the handler, because at
// the time that $modal.on() is called, the $("#shiny-modal") div doesn't
// yet exist.
if ($("#shiny-modal").data("keyboard") === false) return;
if (e.keyCode === 27) {
e.stopPropagation();
e.preventDefault();
}
});
// Set/replace contents of wrapper with html.
exports.renderContent($modal, { html: html, deps: deps });
},
@@ -1556,6 +1740,8 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
remove: function remove() {
var $modal = $('#shiny-modal-wrapper');
$modal.off('keydown.shinymodal');
// Look for a Bootstrap modal and if present, trigger hide event. This will
// trigger the hidden.bs.modal callback that we set in show(), which unbinds
// and removes the element.
@@ -2677,7 +2863,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var aProps = Object.getOwnPropertyNames(a);
var bProps = Object.getOwnPropertyNames(b);
if (aProps.length != bProps.length) return false;
if (aProps.length !== bProps.length) return false;
for (var i = 0; i < aProps.length; i++) {
var propName = aProps[i];
@@ -2797,6 +2983,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
@@ -3079,6 +3270,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
exports.renderContent = function (el, content) {
var where = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "replace";
if (where === "replace") {
exports.unbindAll(el);
}
exports.unbindAll(el);
var html;
@@ -3136,8 +3331,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var $head = $("head").first();
if (dep.meta) {
var metas = $.map(asArray(dep.meta), function (content, name) {
return $("<meta>").attr("name", name).attr("content", content);
var metas = $.map(asArray(dep.meta), function (obj, idx) {
// only one named pair is expected in obj as it's already been decomposed
var name = Object.keys(obj)[0];
return $("<meta>").attr("name", name).attr("content", obj[name]);
});
$head.append(metas);
}
@@ -3274,6 +3471,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
@@ -3645,7 +3850,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
};
}
if (this._numValues(el) == 2) {
if (this._numValues(el) === 2) {
return [convert(result.from), convert(result.to)];
} else {
return convert(result.from);
@@ -3657,7 +3862,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
$el.data('immediate', true);
try {
if (this._numValues(el) == 2 && value instanceof Array) {
if (this._numValues(el) === 2 && value instanceof Array) {
slider.update({ from: value[0], to: value[1] });
} else {
slider.update({ from: value });
@@ -3682,7 +3887,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
var msg = {};
if (data.hasOwnProperty('value')) {
if (this._numValues(el) == 2 && data.value instanceof Array) {
if (this._numValues(el) === 2 && data.value instanceof Array) {
msg.from = data.value[0];
msg.to = data.value[1];
} else {
@@ -4536,7 +4741,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
// from being mistakenly selected)
if ($el.find('i[class]').length > 0) {
var icon_html = $el.find('i[class]')[0];
if (icon_html == $el.children()[0]) {
if (icon_html === $el.children()[0]) {
// another check for robustness
icon = $(icon_html).prop('outerHTML');
}
@@ -4650,9 +4855,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);
@@ -4722,6 +4928,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);
@@ -4796,7 +5022,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));
}
}
@@ -4942,19 +5168,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 = {};
@@ -4965,7 +5199,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);
}
}
@@ -4974,7 +5210,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;
@@ -4988,7 +5224,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 () {
@@ -5017,14 +5260,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() {
@@ -5064,12 +5303,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
@@ -5112,7 +5350,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
@@ -5266,12 +5513,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
@@ -5286,6 +5549,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

@@ -8,7 +8,7 @@ addResourcePath(prefix, directoryPath)
}
\arguments{
\item{prefix}{The URL prefix (without slashes). Valid characters are a-z,
A-Z, 0-9, hyphen, period, and underscore; and must begin with a-z or A-Z.
A-Z, 0-9, hyphen, period, and underscore.
For example, a value of 'foo' means that any request paths that begin with
'/foo' will be mapped to the given directory.}
@@ -32,4 +32,3 @@ addResourcePath('datasets', system.file('data', package='datasets'))
\seealso{
\code{\link{singleton}}
}

View File

@@ -4,10 +4,12 @@
\alias{applyInputHandlers}
\title{Apply input handlers to raw input values}
\usage{
applyInputHandlers(inputs)
applyInputHandlers(inputs, shinysession = getDefaultReactiveDomain())
}
\arguments{
\item{inputs}{A named list of input values.}
\item{shinysession}{A Shiny session object.}
}
\description{
The purpose of this function is to make it possible for external packages to
@@ -21,19 +23,7 @@ like \code{"x:shiny.date"}. This function would apply the \code{"shiny.date"}
input handler to the value, and then rename the result to \code{"x"}, in the
output.
}
\examples{
applyInputHandlers(list(
"m1" = list(list(1, 2), list(3, 4)),
"m2:shiny.matrix" = list(list(1, 2), list(3, 4)),
"d1" = "2016-01-01",
"d2:shiny.date" = "2016-01-01", # Date object
"n1" = NULL,
"n2:shiny.number" = NULL # Converts to NA
))
}
\seealso{
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

@@ -75,7 +75,7 @@ Dedicated functions are available for the most common HTML tags that do not
conflict with common R functions.
The result from these functions is a tag object, which can be converted using
\code{\link{as.character}()}.
\code{\link[base]{as.character}()}.
}
\examples{
doc <- tags$html(

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

@@ -56,7 +56,7 @@ the browser. It allows the following values:
\item \code{yy} Year without century (12)
\item \code{yyyy} Year with century (2012)
\item \code{mm} Month number, with leading zero (01-12)
\item \code{m} Month number, without leading zero (01-12)
\item \code{m} Month number, without leading zero (1-12)
\item \code{M} Abbreviated month name
\item \code{MM} Full month name
\item \code{dd} Day of month with leading zero
@@ -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

@@ -62,7 +62,7 @@ the browser. It allows the following values:
\item \code{yy} Year without century (12)
\item \code{yyyy} Year with century (2012)
\item \code{mm} Month number, with leading zero (01-12)
\item \code{m} Month number, without leading zero (01-12)
\item \code{m} Month number, without leading zero (1-12)
\item \code{M} Abbreviated month name
\item \code{MM} Full month name
\item \code{dd} Day of month with leading zero
@@ -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}}.
}

122
man/debounce.Rd Normal file
View File

@@ -0,0 +1,122 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/reactives.R
\name{debounce}
\alias{debounce}
\alias{throttle}
\title{Slow down a reactive expression with debounce/throttle}
\usage{
debounce(r, millis, priority = 100, domain = getDefaultReactiveDomain())
throttle(r, millis, priority = 100, domain = getDefaultReactiveDomain())
}
\arguments{
\item{r}{A reactive expression (that invalidates too often).}
\item{millis}{The debounce/throttle time window. You may optionally pass a
no-arg function or reactive expression instead, e.g. to let the end-user
control the time window.}
\item{priority}{Debounce/throttle is implemented under the hood using
\link[=observe]{observers}. Use this parameter to set the priority of
these observers. Generally, this should be higher than the priorities of
downstream observers and outputs (which default to zero).}
\item{domain}{See \link{domains}.}
}
\description{
Transforms a reactive expression by preventing its invalidation signals from
being sent unnecessarily often. This lets you ignore a very "chatty" reactive
expression until it becomes idle, which is useful when the intermediate
values don't matter as much as the final value, and the downstream
calculations that depend on the reactive expression take a long time.
\code{debounce} and \code{throttle} use different algorithms for slowing down
invalidation signals; see Details.
}
\details{
This is not a true debounce/throttle in that it will not prevent \code{r}
from being called many times (in fact it may be called more times than
usual), but rather, the reactive invalidation signal that is produced by
\code{r} is debounced/throttled instead. Therefore, these functions should be
used when \code{r} is cheap but the things it will trigger (downstream
outputs and reactives) are expensive.
Debouncing means that every invalidation from \code{r} will be held for the
specified time window. If \code{r} invalidates again within that time window,
then the timer starts over again. This means that as long as invalidations
continually arrive from \code{r} within the time window, the debounced
reactive will not invalidate at all. Only after the invalidations stop (or
slow down sufficiently) will the downstream invalidation be sent.
\code{ooo-oo-oo---- => -----------o-}
(In this graphical depiction, each character represents a unit of time, and
the time window is 3 characters.)
Throttling, on the other hand, delays invalidation if the \emph{throttled}
reactive recently (within the time window) invalidated. New \code{r}
invalidations do not reset the time window. This means that if invalidations
continually come from \code{r} within the time window, the throttled reactive
will invalidate regularly, at a rate equal to or slower than than the time
window.
\code{ooo-oo-oo---- => o--o--o--o---}
}
\section{Limitations}{
Because R is single threaded, we can't come close to guaranteeing that the
timing of debounce/throttle (or any other timing-related functions in
Shiny) will be consistent or accurate; at the time we want to emit an
invalidation signal, R may be performing a different task and we have no
way to interrupt it (nor would we necessarily want to if we could).
Therefore, it's best to think of the time windows you pass to these
functions as minimums.
You may also see undesirable behavior if the amount of time spent doing
downstream processing for each change approaches or exceeds the time
window: in this case, debounce/throttle may not have any effect, as the
time each subsequent event is considered is already after the time window
has expired.
}
\examples{
## Only run examples in interactive R sessions
if (interactive()) {
options(device.ask.default = FALSE)
library(shiny)
library(magrittr)
ui <- fluidPage(
plotOutput("plot", click = clickOpts("hover")),
helpText("Quickly click on the plot above, while watching the result table below:"),
tableOutput("result")
)
server <- function(input, output, session) {
hover <- reactive({
if (is.null(input$hover))
list(x = NA, y = NA)
else
input$hover
})
hover_d <- hover \%>\% debounce(1000)
hover_t <- hover \%>\% throttle(1000)
output$plot <- renderPlot({
plot(cars)
})
output$result <- renderTable({
data.frame(
mode = c("raw", "throttle", "debounce"),
x = c(hover()$x, hover_t()$x, hover_d()$x),
y = c(hover()$y, hover_t()$y, hover_d()$y)
)
})
}
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,11 +3,12 @@
\name{downloadButton}
\alias{downloadButton}
\alias{downloadLink}
\alias{downloadLink}
\title{Create a download button or link}
\usage{
downloadButton(outputId, label = "Download", class = NULL)
downloadButton(outputId, label = "Download", class = NULL, ...)
downloadLink(outputId, label = "Download", class = NULL)
downloadLink(outputId, label = "Download", class = NULL, ...)
}
\arguments{
\item{outputId}{The name of the output slot that the \code{downloadHandler}
@@ -16,6 +17,8 @@ is assigned to.}
\item{label}{The label that should appear on the button.}
\item{class}{Additional CSS classes to apply to the tag, if any.}
\item{...}{Other arguments to pass to the container tag function.}
}
\description{
Use these functions to create a download button or link; when clicked, it
@@ -41,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}}.
}

70
man/exportTestValues.Rd Normal file
View File

@@ -0,0 +1,70 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/test-export.R
\name{exportTestValues}
\alias{exportTestValues}
\title{Register expressions for export in test mode}
\usage{
exportTestValues(..., quoted_ = FALSE, env_ = parent.frame(),
session_ = getDefaultReactiveDomain())
}
\arguments{
\item{...}{Named arguments that are quoted or unquoted expressions that will
be captured and evaluated when snapshot URL is visited.}
\item{quoted_}{Are the expression quoted? Default is \code{FALSE}.}
\item{env_}{The environment in which the expression should be evaluated.}
\item{session_}{A Shiny session object.}
}
\description{
This function registers expressions that will be evaluated when a test export
event occurs. These events are triggered by accessing a snapshot URL.
}
\details{
This function only has an effect if the app is launched in test mode. This is
done by calling \code{runApp()} with \code{test.mode=TRUE}, or by setting the
global option \code{shiny.testmode} to \code{TRUE}.
}
\examples{
## Only run this example in interactive R sessions
if (interactive()) {
options(shiny.testmode = TRUE)
# This application shows the test snapshot URL; clicking on it will
# fetch the input, output, and exported values in JSON format.
shinyApp(
ui = basicPage(
h4("Snapshot URL: "),
uiOutput("url"),
h4("Current values:"),
verbatimTextOutput("values"),
actionButton("inc", "Increment x")
),
server = function(input, output, session) {
vals <- reactiveValues(x = 1)
y <- reactive({ vals$x + 1 })
observeEvent(input$inc, {
vals$x <<- vals$x + 1
})
exportTestValues(
x = vals$x,
y = y()
)
output$url <- renderUI({
url <- session$getTestSnapshotUrl(format="json")
a(href = url, url)
})
output$values <- renderText({
paste0("vals$x: ", vals$x, "\\ny: ", y())
})
}
)
}
}

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

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